
"""\
The plug-in provides the possibility to decode an MMS PDU following the
document OMA-TS-MMS-ENC-V1_3-20050927-C and OMA-WAP-TS-WSP-V1_0-20020920-c
(not all WSP keywords were implemented totally).

Currently, it supports both multipart PDU and Wap Push PDU. Other PDU types
aren't support. Additionally, it supplies an interface for the user to generate
an m-notification-ind PDU based on the opened m-send-req one for the
development of Entry platform. The interface uses the embedded prompt language
to implement the GUI for the user interaction.

And it also provides a cross-plugin interface for the plug-in SMSE to analyze
the Wap push PDU with the invoker MME.DecodePdu. More implemented details can
refer to the source code of the plug-ins.
"""

__version__ = "1.7"
__aof__ = ('MMS Encapsulation', 'MME')
__extension__ = (('MMSE PDU Files', ('*.pdu', '*.mms')),)

_ENABLE_DEBUG = 'Enable Debugging'
_ENABLE_DETAILS = 'Enable Details'

__configs__ = (
    {'name': _ENABLE_DEBUG,
     'attr': 'int'},
    {'name': _ENABLE_DETAILS,
     'attr': 'bool',
     'default': False},
)

import time

# for content-type could refer to:
# http://www.wapforum.org/wina/wsp-content-type.htm
# -------------------------------------------
WspContent = {
    0x00: "*/*",
    0x01: "text/*",
    0x02: "text/html",
    0x03: "text/plain",
    0x04: "text/x-hdml",
    0x05: "text/x-ttml",
    0x06: "text/x-vcalendar",
    0x07: "text/x-vcard",
    0x08: "text/vnd.wap.wml",
    0x09: "text/vnd.wap.wmlscript",
    0x0a: "text/vnd.wap.wta-event",
    0x0b: "multipart/*",
    0x0c: "multipart/mixed",
    0x0d: "multipart/form-data",
    0x0e: "multipart/byterantes",
    0x0f: "multipart/alternative",
    0x10: "application/*",
    0x11: "application/java-vm",
    0x12: "application/x-www-form-urlencoded",
    0x13: "application/x-hdmlc",
    0x14: "application/vnd.wap.wmlc",
    0x15: "application/vnd.wap.wmlscriptc",
    0x16: "application/vnd.wap.wta-eventc",
    0x17: "application/vnd.wap.uaprof",
    0x18: "application/vnd.wap.wtls-ca-certificate",
    0x19: "application/vnd.wap.wtls-user-certificate",
    0x1a: "application/x-x509-ca-cert",
    0x1b: "application/x-x509-user-cert",
    0x1c: "image/*",
    0x1d: "image/gif",
    0x1e: "image/jpeg",
    0x1f: "image/tiff",
    0x20: "image/png",
    0x21: "image/vnd.wap.wbmp",
    0x22: "application/vnd.wap.multipart.*",
    0x23: "application/vnd.wap.multipart.mixed",
    0x24: "application/vnd.wap.multipart.form-data",
    0x25: "application/vnd.wap.multipart.byteranges",
    0x26: "application/vnd.wap.multipart.alternative",
    0x27: "application/xml",
    0x28: "text/xml",
    0x29: "application/vnd.wap.wbxml",
    0x2a: "application/x-x968-cross-cert",
    0x2b: "application/x-x968-ca-cert",
    0x2c: "application/x-x968-user-cert",
    0x2d: "text/vnd.wap.si",
    0x2e: "application/vnd.wap.sic",
    0x2f: "text/vnd.wap.sl",
    0x30: "application/vnd.wap.slc",
    0x31: "text/vnd.wap.co",
    0x32: "application/vnd.wap.coc",
    0x33: "application/vnd.wap.multipart.related",
    0x34: "application/vnd.wap.sia",
    0x35: "text/vnd.wap.connectivity-xml",
    0x36: "application/vnd.wap.connectivity-wbxml",
    0x37: "application/pkcs7-mime",
    0x38: "application/vnd.wap.hashed-certificate",
    0x39: "application/vnd.wap.signed-certificate",
    0x3a: "application/vnd.wap.cert-response",
    0x3b: "application/xhtml+xml",
    0x3c: "application/wml+xml",
    0x3d: "text/css",
    0x3e: "application/vnd.wap.mms-message",
    0x3f: "application/vnd.wap.rollover-certificate",
    0x40: "application/vnd.wap.locc+wbxml",
    0x41: "application/vnd.wap.loc+xml",
    0x42: "application/vnd.syncml.dm+wbxml",
    0x43: "application/vnd.syncml.dm+xml",
    0x44: "application/vnd.syncml.notification",
    0x45: "application/vnd.wap.xhtml+xml",
    0x46: "application/vnd.wv.csp.cir",
    0x47: "application/vnd.oma.dd+xml",
    0x48: "application/vnd.oma.drm.message",
    0x49: "application/vnd.oma.drm.content",
    0x4a: "application/vnd.oma.drm.rights+xml",
    0x4b: "application/vnd.oma.drm.rights+wbxml",
    0x4c: "application/vnd.wv.csp+xml",
    0x4d: "application/vnd.wv.csp+wbxml",
    # 3rd party register
    0x0201: "application/vnd.uplanet.cachop-wbxml",
    0x0202: "application/vnd.uplanet.signal",
    0x0203: "application/vnd.uplanet.alert-wbxml",
    0x0204: "application/vnd.uplanet.list-wbxml",
    0x0205: "application/vnd.uplanet.listcmd-wbxml",
    0x0206: "application/vnd.uplanet.channel-wbxml",
    0x0207: "application/vnd.uplanet.provisioning-status-uri",
    0x0208: "x-wap.multipart/vnd.uplanet.header-set",
    0x0209: "application/vnd.uplanet.bearer-choice-wbxml",
    0x020A: "application/vnd.phonecom.mmc-wbxml",
    0x020B: "application/vnd.nokia.syncset+wbxml",
    0x020C: "image/x-up-wpng",
    0x0300: "application/iota.mmc-wbxml",
    0x0301: "application/iota.mmc-xml"
}

# Table 42, OMA-WAP-TS-WSP-V1_0-20020920-C
# ref ftp://ftp.isi.edu/in-notes/iana/assignments/character-sets
# ref http://www.iana.org/assignments/character-sets
# -------------------------------------------
WspCharacterSetAssignment = {
    0x03e8: 'iso-10646-ucs-2',         0x03e9: 'iso-10646-ucs-4',
    0x03ea: 'iso-10646-ucs-basic',     0x03eb: 'iso-10646-unicode-latin1',
    0x03ec: 'iso-10646-j-1',           0x03ed: 'iso-unicode-ibm-1261',
    0x03ee: 'iso-unicode-ibm-1268',    0x03ef: 'iso-unicode-ibm-1276',
    0x03f0: 'iso-unicode-ibm-1264',    0x03f1: 'iso-unicode-ibm-1265',
    0x03f2: 'unicode-1-1',             0x03f3: 'scsu',
    0x03f4: 'utf-7',                   0x03f5: 'utf-16be',
    0x03f6: 'utf-16le',                0x03f7: 'utf-16',
    0x03f8: 'cesu-8',                  0x03f9: 'utf-32',
    0x03fa: 'utf-32be',                0x03fb: 'utf-32le',
    0x03fc: 'bocu-1',
    0x07da: 'ibm-852',                 0x07db: 'ibm-437',
    0x07e9: 'gb2312',
    0x07ea: 'big5',                    0x07eb: 'macintosh',
    0x07ec: 'ibm-037',                 0x07ed: 'ibm-038',
    0x07ee: 'ibm-273',                 0x07ef: 'ibm-274',
    0x07f0: 'ibm-275',                 0x07f1: 'ibm-277',
    0x07f2: 'ibm-278',                 0x07f3: 'ibm-280',
    0x07f4: 'ibm-281',                 0x07f5: 'ibm-284',
    0x07f6: 'ibm-285',                 0x07f7: 'ibm-290',
    0x07f8: 'ibm-297',                 0x07f9: 'ibm-420',
    0x07fa: 'ibm-423',                 0x07fb: 'ibm-424',
    0x07fc: 'ibm-500',                 0x07fd: 'ibm-851',
    0x07fe: 'ibm-855',                 0x07ff: 'ibm-857',
    0x0800: 'ibm-860',                 0x0801: 'ibm-861',
    0x0802: 'imb-863',                 0x0803: 'ibm-864',
    0x0804: 'ibm-865',                 0x0805: 'ibm-868',
    0x0806: 'ibm-869',                 0x0807: 'ibm-870',
    0x0808: 'ibm-871',                 0x0809: 'ibm-880',
    0x080a: 'ibm-891',                 0x080b: 'ibm-903',
    0x080c: 'ibm-904',                 0x080d: 'ibm-905',
    0x080e: 'ibm-918',                 0x080f: 'ibm-1026',
    0x0810: 'ebcdic-at-ed',            0x0811: 'ebcdic-at-de-a',
    0x0812: 'ebcdic-ca-fr',            0x0813: 'ebcdic-dk-no',
    0x0814: 'ebcdic-dk-no-a',          0x0815: 'ebcdic-fi-se',
    0x0816: 'ebcdic-fi-se-a',          0x0817: 'ebcdic-fr',
    0x0818: 'ebcdic-it',               0x0819: 'ebcdic-pt',
    0x081a: 'ebcdic-es',               0x081b: 'ebcdic-es-a',
    0x081c: 'ebcdic-es-s',             0x081d: 'ebcdic-unk',
    0x081e: 'ebcdic-us',               0x081f: 'unknonw-8bit',
    0x0820: 'mnemonic',                0x0821: 'mnem',
    0x0822: 'viscii',                  0x0823: 'viqr',
    0x0824: 'koi8-r',                  0x0825: 'kz-gb-2312',
    0x0826: 'ibm-866',                 0x0827: 'ibm-755',
    0x0828: 'koi8-u',                  0x0829: 'ibm-00858',
    0x082a: 'ibm-00924',               0x082b: 'ibm-01140',
    0x082c: 'ibm-01141',               0x082d: 'ibm-01142',
    0x082e: 'ibm-01143',               0x082f: 'ibm-01144',
    0x0830: 'ibm-01145',               0x0831: 'ibm-01146',
    0x0832: 'ibm-01147',               0x0833: 'ibm-01148',
    0x0834: 'ibm-01149',               0x0835: 'big5-hkscs',
    0x0836: 'ibm-1047',                0x0837: 'ptcp154',
    0x0838: 'amiga-1251',              0x0839: 'koi7-switched',
    0x083a: 'brf',                     0x083b: 'tscii',
    0x08ca: 'windows-1250',            0x08cb: 'windows-1251',
    0x08cc: 'windows-1252',            0x08cd: 'windows-1253',
    0x08ce: 'windows-1254',            0x08cf: 'windows-1255',
    0x08d0: 'windows-1526',            0x08d1: 'windows-1527',
    0x08d2: 'windows-1258',            0x08d3: 'tis-620',
    0x08d4: 'cp50220',
    0x00: '*',                    0x03: 'us-ascii',
    0x04: 'iso-8859-1',           0x05: 'iso-8859-2',
    0x06: 'iso-8859-3',           0x07: 'iso-8859-4',
    0x08: 'iso-8859-5',           0x09: 'iso-8859-6',
    0x0a: 'iso-8859-7',           0x0b: 'iso-8859-8',
    0x0c: 'iso-8859-9',           0x0d: 'iso-8859-10',
    0x0e: 'iso_6937-2-add',       0x0f: 'JIS_X0201',
    0x10: 'JIS_Encoding',         0x11: 'Shift_JIS',
    0x12: 'EUC-JP',               0x13: 'csEUCFixWidJapanese',
    0x14: 'iso646-gb',            0x15: 'iso646-se2',
    0x16: 'iso646-it',            0x17: 'iso646-es',
    0x18: 'iso646-de',            0x19: 'iso646-no',
    0x1a: 'iso646-fr',            0x1b: 'iso-10646-utf-1',
    0x1c: 'iso646.basic',         0x1d: 'csINVARIANT',
    0x1e: 'iso646.irv',           0x1f: 'nats-sefi',
    0x20: 'nats-sefi-add',        0x21: 'nats-dano',
    0x22: 'nats-dano-add',        0x23: 'sen_850200_b',
    0x24: 'korean',               0x25: 'iso-2022-kr',
    0x26: 'euc-kr',               0x27: 'iso-2022-jp',
    0x28: 'iso-2022-jp-2',        0x29: 'jis_c6220-1969-jp',
    0x2a: 'iso646-jp',            0x2b: 'iso646-pt',
    0x2c: 'greek7-old',           0x2d: 'latin-greek',
    0x2e: 'iso646-fr1',           0x2f: 'latin-greek-1',
    0x30: 'iso-5427',             0x31: 'jis_c6226-1978',
    0x32: 'bs_viewdata',          0x33: 'inis',
    0x34: 'inis-8',               0x35: 'inis-cyrillic',
    0x36: 'iso_5427:1981',        0x37: 'iso_5428:1980',
    0x38: 'iso646-cn',            0x39: 'gb_2312-80',
    0x3a: 'iso646-no2',           0x3b: 'videotex-suppl',
    0x3c: 'iso646-pt2',           0x3d: 'iso646-es2',
    0x3e: 'iso646-hu',            0x3f: 'jis_c6226-1983',
    0x40: 'greek7',               0x41: 'arabic7',
    0x42: 'iso-ir-90',            0x43: 'jp-ocr-a',
    0x44: 'jp-ocr-b',             0x45: 'jp-ocr-b-add',
    0x46: 'jp-ocr-hand',          0x47: 'jis-c6229-1984-kana',
    0x48: 'jis_c6229-1984-kana',  0x49: 'iso-2033-1983',
    0x4a: 'ansi_x3.110-1983',     0x4b: 't.61-7bit',
    0x4c: 't.61-8bit',            0x4d: 'koi8-e',
    0x4e: 'csa7-1',               0x4f: 'csa7-2',
    0x50: 'csa_z243.4-1985-gr',   0x51: 'iso_8859-6-e',
    0x52: 'iso_8859-6-i',         0x53: 't.101-g2',
    0x54: 'iso_8859-8-e',         0x55: 'iso_8859-i-i',
    0x56: 'csn_369103',           0x57: 'jus_i.b1.002',
    0x58: 'iec_p27-1',            0x59: 'serbian',
    0x5a: 'macedonian',           0x5b: 'greek-ccitt',
    0x5c: 'cuba',                 0x5d: 'iso_6937-2-25',
    0x5e: 'gost_19768-74',        0x5f: 'iso_8859-supp',
    0x60: 'iso_10367-box',        0x61: 'latin-lap',
    0x62: 'jis_x0212-1990',       0x63: 'iso646-dk',
    0x64: 'us-dk',                0x65: 'dk-us',
    0x66: 'iso646-kr',            0x67: 'unicode-1-1-utf-7',
    0x68: 'iso-2022-cn',          0x69: 'iso-2022-cn-ext',
    0x6a: 'utf-8',
    0x6d: 'iso-8859-13',
    0x6e: 'iso-8859-14',          0x6f: 'iso-8859-15',
    0x70: 'iso-8859-16',          0x71: 'gbk',
    0x72: 'gb18030',              0x73: 'osd_ebcdic_df04_15',
    0x74: 'osd_ebcdic_df03_irv',  0x75: 'osd_ebcdic_df04_1',
    0x76: 'iso-11548-1',          0x77: 'kz-1048',
    0x80: 'Any-Charset'
}

FieldTpQValue = 0x01
FieldTpWellKnownCharset = 0x02
FieldTpVersionValue = 0x03
FieldTpInteger = 0x04
FieldTpTextString = 0x05
FieldTpShortInteger = 0x06
FieldTpConsrtainedEnc = 0x07
FieldTpDeltaSecond = 0x08
FieldTpNoValue = 0x09
FieldTpTextValue = 0x0a
FieldTpUriValue = 0x0b
FieldTpFieldName = 0x0c
FieldTpDateValue = 0x0d
FieldTpLongInteger = 0x0e
FieldTpValueLength = 0x0f
FieldTpYesOrNo = 0x10
FieldTpUintVar = 0x20


def _Tp2Subject(tp):
    _SUBJECT = {
        FieldTpQValue: 'Q-value',
        FieldTpWellKnownCharset: 'charset',
        FieldTpVersionValue: 'version',
        FieldTpInteger: 'value',
        FieldTpTextString: 'text',
        FieldTpShortInteger: 'value',
        FieldTpTextValue: 'text',
        FieldTpUriValue: 'uri',
        FieldTpLongInteger: 'value',
        FieldTpValueLength: 'length',
        FieldTpYesOrNo: 'value',
        FieldTpUintVar: 'uint'
    }

    return _SUBJECT.get(tp, 'unknown')


global _FIELD_ASSIGNMENT
global _WSP_HEADER_ASSIGNMENT

_WSP_WELLKNOWN_ASSIGNMENT = {
    0x00: ("Q", FieldTpQValue),
    0x01: ("Charset", FieldTpWellKnownCharset),
    0x02: ("Level", FieldTpVersionValue),
    0x03: ("Type", FieldTpInteger),
    0x05: ("Name", FieldTpTextString),
    0x06: ("Filename", FieldTpTextString),
    0x07: ("Differences", FieldTpFieldName),
    0x08: ("Padding", FieldTpShortInteger),
    0x09: ("Type", FieldTpConsrtainedEnc),  # multipart
    0x0a: ("Start", FieldTpTextString),  # multipart
    0x0b: ("Start-info", FieldTpTextString),  # multipart
    0x0c: ("Comment", FieldTpTextString),
    0x0d: ("Domain", FieldTpTextString),
    0x0e: ("Max-Age", FieldTpDeltaSecond),
    0x0f: ("Path", FieldTpTextString),
    0x10: ("Secure", FieldTpNoValue),
    0x11: ("SEC", FieldTpShortInteger),
    0x12: ("MAC", FieldTpTextValue),
    0x13: ("Creation-date", FieldTpDateValue),
    0x14: ("Modification-date", FieldTpDateValue),
    0x15: ("Read-date", FieldTpDateValue),
    0x16: ("Size", FieldTpInteger),
    0x17: ("Name", FieldTpTextValue),
    0x18: ("Filename", FieldTpTextValue),
    0x19: ("Start", FieldTpTextValue),  # multipart
    0x1a: ("Start-info", FieldTpTextValue),  # multipart
    0x1b: ("Comment", FieldTpTextValue),
    0x1c: ("Domain", FieldTpTextValue),
    0x1d: ("Path", FieldTpTextValue)
}


def _newFieldObject(target, kws, start=-1, size=0, fixed=0):
    if start == -1:
        start = kws['start'] + kws['offset']

    if fixed != 0 and start >= fixed:
        start -= fixed

    if size == 0:
        size = kws['start'] + kws['offset'] - start

    return target.newFieldObject(start, size)


def GetTextString(target, source, kws):
    'get an ASCIIZ string'
    s = target.readString(source, **kws)
    kws['offset'] += len(s) + 1

    return s


def readWithOffset(target, source, size, kws, fixed=True):
    ret = target.readFile(source, size, **kws)
    if fixed:
        kws['offset'] += size

    return ret


def PduFieldParse(tp, target, source, kws):
    ret, content, field = True, None, None
    # - refer 8.4.2.8
    # Well-known-charset = Any-charset | Integer-value
    # Any-charset = <Octet 128>
    offset = kws['offset']
    if tp == FieldTpWellKnownCharset:
        va = ord(readWithOffset(target, source, 1, kws))
        if va != 0x80:
            kws['offset'] -= 1
            ret, va, field = PduFieldParse(FieldTpInteger, target, source, kws)

        if ret:
            content = WspCharacterSetAssignment.get(va, '0x%02x' % va)

    # - refer to 8.4.2.3
    # Q-value = 1*2 OCTECT
    elif tp == FieldTpQValue:
        ret, va, field = PduFieldParse(FieldTpUriValue, target, source, kws)
        content = '0.%d' % va
    # - refer to 8.4.2.3
    # Version-value = Short-integer | Text-string
    elif tp == FieldTpVersionValue:
        va = ord(readWithOffset(target, source, 1, kws))
        if va & 0x80:
            ma, mi = (va & 0x7f) >> 4, va & 0x0f
            if mi == 15:
                content = '%d' % ma
            else:
                content = '%d.%d' % (ma, mi)
        else:
            content = GetTextString(target, source, kws)
    # - refer to 8.4.2.3
    # Long-integer = Short-length Multi-octet-integer
    # Short-length = <Any octect 0-30>
    # Multi-octet-integer = 1*30 OCTECT
    elif tp == FieldTpLongInteger:
        va = ord(readWithOffset(target, source, 1, kws))
        if va > 30:
            ret = False
        else:
            content = 0
            for _ in range(va):
                content = content * 256 + ord(readWithOffset(target,
                                              source, 1, kws))
    # - refer to 8.4.2.3
    # Integer-value = Short-integer | Long-integer
    # Short-integer = OCTECT
    # Long-integer = Short-length Multi-octet-integer
    # Short-length = <Any octect 0-30>
    # Multi-octet-integer = 1*30 OCTECT
    # - refer to 8.4.2.3
    # Delta-seconds-value = Integer-value
    elif tp == FieldTpInteger or tp == FieldTpDeltaSecond:
        va = ord(readWithOffset(target, source, 1, kws, False))
        # Short-integer
        if va & 0x80:
            content = va & 0x7f
            kws['offset'] += 1
        # Long-integer
        else:
            ret, content, field = PduFieldParse(FieldTpLongInteger,
                                                target, source, kws)
    # - refer to 8.4.2.3
    # Short-integer = OCTECT
    elif tp == FieldTpShortInteger:
        va = ord(readWithOffset(target, source, 1, kws))
        if va & 0x80:
            content = va
        else:
            ret = False
    # - refer 8.4.2.2
    # Value-length = Short-length | (Length-quote Length)
    elif tp == FieldTpValueLength:
        va = ord(readWithOffset(target, source, 1, kws))
        if va == 0x1f:
            ret, content, field = PduFieldParse(FieldTpUintVar,
                                                target, source, kws)
        elif va < 0x1f:
            content = va
        else:
            ret = False
    # - refer 8.4.2.1
    # Text-string = [Quote] *TEXT End-of-string
    elif tp == FieldTpTextString:
        content = GetTextString(target, source, kws)
        if len(content) > 0 and content[0] == '"':
            content = content[1:]
    # - refer 8.4.2.1
    # Uri-value = Text-string
    elif tp == FieldTpUriValue:
        content = GetTextString(target, source, kws)
    # - refer 8.4.2.6
    # Field-name = Token-text | Well-known-field-name
    # Well-known-field-name = Short-integer
    # - refer to 8.4.2.1
    # Token-text = Token End-of-string
    elif tp == FieldTpFieldName:
        va = ord(readWithOffset(target, source, 1, kws, fixed=False))
        if va & 0x80:
            content = va
            kws['offset'] += 1
        else:  # FIXME: treat it as a string?
            content = GetTextString(target, source, kws)
    # - refer 8.4.2.1
    # constrained-encoding = Extension-Media | Short-integer
    # Extension-Media = *TEXT End-of-string
    elif tp == FieldTpConsrtainedEnc:
        va = ord(readWithOffset(target, source, 1, kws, fixed=False))
        if va & 0x80:
            content = va
            kws['offset'] += 1
        else:
            content = GetTextString(target, source, kws)
    # - refer to 8.4.2.3
    # Text-value = No-Value | Token-text | Quoted-string
    elif tp == FieldTpTextValue:
        content = GetTextString(target, source, kws)
    # - refer to 8.4.2.3
    # Date-value = Long-integer
    elif tp == FieldTpDateValue:
        ret, content, field = PduFieldParse(FieldTpLongInteger,
                                            target, source, kws)
        # convert the time to string
        if ret:
            try:
                content = time.ctime(content)
            except ValueError:
                ret = False
    # - refer to 7.3.13 of OMA-TS-MMS-ENC-V1_3-20050927-C
    # Yes | No
    elif tp == FieldTpYesOrNo:
        va = ord(readWithOffset(target, source, 1, kws))
        if va == 0x80:
            content = 'Yes (0x80)'
        elif va == 0x81:
            content = 'No (0x81)'
        else:
            ret = False
    # convert calculate the ascii to a uint variable can use the method
    # ascii / 128 = high -> if high < 128 => high | 0x80
    # ascii & 0x7f = low -> uinit = (hight | 0x80), low
    elif tp == FieldTpUintVar:
        size, content = target.readVariableValue(source, **kws)
        kws['offset'] += size
    # - refer to 8.4.2.3
    # No-value = <Octect 0>
    elif tp == FieldTpNoValue:
        va = ord(readWithOffset(target, source, 1, kws))
        if va == 0:
            content = "<Octect 0>"
        else:
            ret = False
    else:
        ret = False

    if field is None:
        field = _newFieldObject(target, kws, offset)

    if not ret:
        kws['offset'] = offset

    return ret, content, field


def _assObject(target, source, kws, text='', field=None, table=None):
    ass = ord(readWithOffset(target, source, 1, kws)) & 0x7f
    if table is None:
        table = _FIELD_ASSIGNMENT

    if ass in table:
        objectp = target.newItemObject(table.get(ass)[0], text)
        if field is not None:
            objectp.update(field=field)
        return objectp
    else:
        kws['offset'] -= 1
        return None


def FieldEncParse(entry, target, source, kws, tp, subject=None, table=None):
    offset = kws['offset']
    if table is None:
        table = _FIELD_ASSIGNMENT

    objectp = _assObject(target, source, kws, table=table)
    ret, text, field = PduFieldParse(tp, target, source, kws)
    if ret:
        if subject is None:
            subject = _Tp2Subject(tp)

        objectp.appendChildren(
            target.newItemObject(subject, text, field=field))
        objectp.update(
            content=text, field=_newFieldObject(target, kws, offset))
        entry.appendChildren(objectp)

    return ret


# - ref 8.4.2.4 of OMA-WAP-TS-WSP-V1_0-20020920-c
# parameter = Typed-parameter | untyped-parameter
# typed-parameter = well-known-parameter-token typed-Value
# -------------------------------------------
def WapWspParameter(entry, target, source, kws, length=0, has_entry=False):
    offset = kws['offset']

    ret, contents = True, list()
    objectp = target.newItemObject('parameter')
    while ret and kws['offset'] < offset + length:
        ass = ord(readWithOffset(target, source, 1, kws, False))
        if ass & 0x80:
            ass &= 0x7f
            afield = _newFieldObject(target, kws, size=1)

            kws['offset'] += 1
            ret = ass in _WSP_WELLKNOWN_ASSIGNMENT
            if ret:
                key, tp = _WSP_WELLKNOWN_ASSIGNMENT[ass]
                if kws['configs'].get(_ENABLE_DEBUG):
                    print '0x%02x: %s -> %d' % (ass, key, tp)

                ret, value, field = PduFieldParse(tp, target, source, kws)
                if ret:
                    contents.append('%s=%s' % (key, value))
                    obj = target.newItemObject(
                        key, value, field=afield + field)

                    objectp.appendChildren(obj)
                    if kws['configs'].get(_ENABLE_DEBUG):
                        obj.appendChildren(
                            target.newItemObject(
                                key, '0x%02x' % ass, field=afield))
        else:
            # Media-type = Extension-Media ...
            ret, text, field = PduFieldParse(FieldTpTextString,
                                             target, source, kws)
            if ret:
                ret, value, field2 = PduFieldParse(FieldTpInteger,
                                                   target, source, kws)
                if not ret:
                    # if failed, treat it as a string
                    ret, value, field2 = PduFieldParse(FieldTpTextString,
                                                       target, source, kws)
                if ret:
                    obj = target.newItemObject(text, value,
                                               field=field + field2)
                    objectp.appendChildren(obj)
                    obj.appendChildren(target.newItemObject('media',
                                       text, field=field))
                    obj.appendChildren(target.newItemObject('value',
                                       value, field=field2))

    if ret and length > 0:
        objectp.update(
            content=';'.join(contents),
            field=_newFieldObject(target, kws, offset))
        if has_entry:
            entry.appendChildren(objectp)
        else:
            entry.append(objectp)

    return ret


# - ref 8.4.2.7 of OMA-WAP-TS-WSP-V1_0-20020920-c
# Accept-value = Constrained-media | Accept-general-form
# Accept-general-form = Value-length Media-range [Accept-parameters]
# Media-range = (well-known-media | Extension-Media) *(Parameter)
# Accept-parameters = Q-token Q-value *(Accept-extension)
# Accept-extension = Parameter
# Constrained-media = Constrained-encoding
# Well-known-media = Integer-value
# -------------------------------------------
def WapWspAccept(entry, target, source, kws, length=0):
    contents = list()
    offset = kws['offset']
    objectp = _assObject(target, source, kws, table=_WSP_HEADER_ASSIGNMENT)

    value = ord(target.readFile(source, 1, **kws))
    if value & 0x80 == 0:
        # Content-general-form = Value-length Media-type
        ret, length, field = PduFieldParse(FieldTpValueLength,
                                           target, source, kws)
        if ret:
            objectp.append(target.newItemObject('length', length, field=field))
            value = ord(target.readFile(source, 1, **kws))

    if value == 0x80:
        # Accept-parameters = Q-token Q-value *(Accept-extension)
        kws['offset'] += 1
        ret, token, field = PduFieldParse(FieldTpQValue, target, source, kws)
        if ret:
            objectp.append(target.newItemObject('q-token', token, field=field))
    elif value & 0x80:
        # Media-type = (well-known-media | Extension-Media)* (Parameter)
        value &= 0x7f
        kws['offset'] += 1
        ret = value in WspContent
        if ret:
            key = WspContent[value]
            contents.append(key)
            objectp.append(
                target.newItemObject(
                    'type', key,
                    field=_newFieldObject(target, kws, 1)))
    else:
        # Media-type = Extension-Media ...
        ret, key, field = PduFieldParse(FieldTpTextString, target, source, kws)
        if ret:
            objectp.append(target.newItemObject('type', key, field=field))
            ret = WapWspParameter(objectp, target, source, kws, has_entry=True,
                                  length=length - (kws['offset'] - offset))

    if ret:
        objectp.update(content=';'.join(contents),
                       field=_newFieldObject(target, kws, offset))

    return ret


# - refer 7.3.11 of OMA-TS-MMS-ENC-V1_3-20050927-C
# - refer 8.2.4 of OMA-WAP-TS-WSP-V1_0-20020920-C
# Content-type-value = Constrained-media | Content-general-form
# Constrained-media = Constrained-encoding
# Constrained-encoding = Extension-Media | Short-integer
# Content-general-form = Value-length Media-type
# Media-type = (well-known-media | Extension-Media)* (Parameter)
# - refer 8.4.2.7 of OMA-WAP-TS-WSP-V1_0-20020920-C
# well-known-media = Integer-value
# Extension-Media = *TEXT End-of-string
# -------------------------------------------
def WapWspContentType(entry, target, source, kws, wsp=False):
    offset = kws['offset']
    contents = list()

    if wsp:
        objectp = target.newItemObject('Content-Type')
    else:
        objectp = _assObject(target, source, kws)

    if objectp is None:
        return False, contents

    # Constrained-encoding = Extension-Media | Short-integer
    ret, value, field = PduFieldParse(FieldTpShortInteger, target, source, kws)
    if ret:
        wellknown = value & 0x7f if field.size == 1 else value
        if wellknown in WspContent:
            content = WspContent.get(wellknown)
            objectp.update(content='%s' % content)
            contents.append(content)

        objectp.appendChildren(target.newItemObject('short-integer',
                               value, field=field))
    else:
        # Content-general-form = Value-length Media-type
        ret, length, field = PduFieldParse(FieldTpValueLength,
                                           target, source, kws)
        if ret:
            if kws['configs'].get(_ENABLE_DETAILS):
                objectp.appendChildren(target.newItemObject('length',
                                       length, field=field))

            koff = kws['offset']
            # Media-type = (well-known-media | Extension-Media)* (Parameter)
            # well-known-media = Integer-value
            # Extension-Media = *TEXT End-of-string
            ret, value, field = PduFieldParse(FieldTpInteger,
                                              target, source, kws)
            if ret:
                value &= 0x7f
                ret = value in WspContent
                if ret:
                    content = WspContent[value]
                    contents.append(content)
                    objectp.appendChildren(target.newItemObject('type',
                                           content, field=field))
            else:
                ret, extension, field = PduFieldParse(FieldTpTextString,
                                                      target, source, kws)
                if ret:
                    contents.append(extension)
                    objectp.update(content=extension)
                    objectp.appendChildren(
                        target.newItemObject(
                            'extension-media', extension, field=field))

            ret = WapWspParameter(objectp, target, source, kws, has_entry=True,
                                  length=length - (kws['offset'] - koff))
        else:
            # Constrained-encoding = Extension-Media | Short-integer
            # Extension-Media = *TEXT End-of-string
            ret, extension, field = PduFieldParse(FieldTpTextString,
                                                  target, source, kws)
            if ret:
                contents.append(extension)
                objectp.update(content=extension)
                objectp.appendChildren(
                    target.newItemObject(
                        'extension-media', extension, field=field))

    if ret:
        # set multipart if the content has the string
        for c in contents:
            if c.find('multipart') > 0:
                kws['multipart'] = True
                break

        content = ';'.join(contents)
        objectp.update(content=content,
                       field=_newFieldObject(target, kws, offset))

        entry.update(content=content)
        entry.appendChildren(objectp)

    return ret, contents


# - ref 8.4.2.8 of OMA-WAP-TS-WSP-V1_0-20020920-c
# Accept-charset-value = Constrained-charset | Accept-charset-general-form
# Accept-charset-general-form = Value-length (well-known-charset
#                                             | Token-text) [Q-Value]
# Constrained-charset = Any-charset | constrained-encoding
# well-known-charset = Any-charset | Integer-Value
# Any-charset = <Octet 128>
# -------------------------------------------
def WapWspAcceptCharset(entry, target, source, kws, length=0):
    offset = kws['offset']

    contents = list()
    objectp = _assObject(target, source, kws, table=_WSP_HEADER_ASSIGNMENT)

    value = ord(target.readFile(source, 1, **kws))
    if value != 0x80:
        # Accept-charset-general-form = Value-length (well-known-charset
        #                                             | Token-text) [Q-Value]
        objectp.appendChildren(target.newItemObject('length', value,
                               field=_newFieldObject(target, kws, 1)))
        kws['offset'] += 1

    ret, charset, field = PduFieldParse(FieldTpWellKnownCharset,
                                        target, source, kws)
    if ret:
        objectp.appendChildren(target.newItemObject('charset',
                               charset, field=field))
        objectp.update(
            content=';'.join(contents),
            field=_newFieldObject(target, kws, offset))

    return ret


def WapWspIdentifiedValue(entry, target, source, kws, subject, maps, cb=None):
    offset = kws['offset']

    objectp = _assObject(target, source, kws, table=_WSP_HEADER_ASSIGNMENT)
    value = ord(readWithOffset(target, source, 1, kws))
    if value in maps:
        text = maps(value)
    elif cb is not None:
        kws['offset'] -= 1
        text = cb(entry, target, source, kws, value)
    else:
        text = 'unknown'

    objectp.appendChildren(target.newItemObject(subject, text,
                           field=_newFieldObject(target, kws, offset + 1)))
    objectp.update(content='%s (0x%02x)' % (text, value),
                   field=_newFieldObject(target, kws, offset))
    entry.appendChildren(objectp)

    return True


# - ref 8.4.2.9
# Acept-encoding-value = Content-encoding-value | Accept-encoding-general-form
# Accept-encoding-general-form = Value-length (Content-encoding-value
#                                              | Any-encoding) [Q-Value]
# Any-encoding = <Octect 131>
# ref 8.4.2.18
# Content-encoding-value = (Gzip | Compress | Deflate | Token-text)
# -------------------------------------------
def WapWspAcceptEncoding(entry, target, source, kws, length=0):
    _ENCODING = {
        0x80: 'Gzip',        0x81: 'Compress',
        0x82: 'Deflate',     0x83: 'Any'
    }

    ret = True
    offset = kws['offset']

    objectp = _assObject(target, source, kws, table=_WSP_HEADER_ASSIGNMENT)
    length = ord(readWithOffset(target, source, 1, kws))
    objectp.appendChildren(target.newItemObject('length', length,
                           field=_newFieldObject(target, kws, offset)))

    # try content-encoding-value
    encoding = ord(readWithOffset(target, source, 1, kws))
    if encoding in _ENCODING:
        objectp.appendChildren(target.newItemObject('encoding', encoding,
                               field=_newFieldObject(target, kws, offset)))
    else:
        ret = False

    left = length - (kws['offset'] - offset)
    if left > 0:
        ret, qvalue, field = PduFieldParse(FieldTpQValue, target, source, kws)
        if ret:
            objectp.appendChildren(target.newItemObject('value',
                                   qvalue, field=field))

    if ret:
        objectp.update(content='encoding=%s' % encoding,
                       field=_newFieldObject(target, kws, offset))
        entry.appendChildren(objectp)

    return ret


# - ref 8.4.2.10 of OMA-WAP-TS-WSP-V1_0-20020920-c
# Accpet-language-value = Constrained-language | Accept-language-general-form
# Accept-language-general-form = Value-length (Well-known-language
#                                              | Text-string)[Q-value]
# Constrained-language = Any-language | Constrained-encoding
# Well-known-language = Any-language | Integer-value
# Any-language = <Octet 128>
# -------------------------------------------
def WapWspAcceptLanguage(entry, target, source, kws, length=0):
    _ISO_639_TABLE = {
        0x01: 'Afar',      0x02: 'Abkhazian',
        0x03: 'Afrikaans', 0x04: 'Amharic',
        0x05: 'Arabic',    0x06: 'Assamese',
        0x07: 'Aymara',    0x08: 'Azerbaijani',
        0x09: 'Bashkir',   0x0a: 'Byelorussian',
        0x0b: 'Bulgarian', 0x0c: 'Bihari',
        0x0d: 'Bislama',   0x0e: 'Bengali',
        0x0f: 'Tibetan',   0x10: 'Breton',
        0x11: 'Breton',    0x12: 'Catelan',
        0x13: 'Corsican',  0x14: 'Czech',
        0x15: 'Welsh',     0x16: 'Danish',
        0x17: 'German',    0x18: 'Bhutani',
        0x19: 'English',   0x1a: 'Esperanto',
        0x1b: 'Spanish',   0x1c: 'Estonian',
        0x1d: 'Basque',    0x1e: 'Persian',
        0x1f: 'Finnish',   0x20: 'Fiji',
        0x21: 'Urdu',      0x22: 'French',
        0x23: 'Uzbek',     0x24: 'Irish',
        0x25: 'Scotos Gaelic', 0x26: 'Galician',
        0x27: 'Guarani',   0x28: 'Gujarati',
        0x29: 'Hausa',     0x2a: 'Hebrew',
        0x2b: 'Hindi',     0x2c: 'Croatian',
        0x2d: 'Hungarian', 0x2e: 'Armenian',
        0x2f: 'Vietnamese', 0x30: 'Indonesian',
        0x31: 'Wolof',     0x32: 'Xhosa',
        0x33: 'Icelandic', 0x34: 'Italian',
        0x35: 'Yoruba',    0x36: 'japanese',
        0x37: 'Javanese',  0x38: 'Georgian',
        0x39: 'Kazakh',    0x3a: 'Zhuang',
        0x3b: 'Cambodian', 0x3c: 'Kannada',
        0x3d: 'Korean',    0x3e: 'Kashmiri',
        0x3f: 'Kurdish',   0x40: 'Kirghiz',
        0x41: 'Chinese',   0x42: 'Lingala',
        0x43: 'Laothian',  0x44: 'Lithuanian',
        0x45: 'Latvian',   0x46: 'Malagasy',
        0x47: 'Maori',     0x48: 'Macedonian',
        0x49: 'Malyalam',  0x4a: 'Mongolia',
        0x4b: 'Moldavian', 0x4c: 'Marathi',
        0x4d: 'Malay',     0x4e: 'Maltese',
        0x4f: 'Burmese',   0x50: 'Ukrainian',
        0x51: 'Nepali',    0x52: 'Dutch',
        0x53: 'Norwegian', 0x54: 'Occitan',
        0x55: 'Oromo',     0x56: 'Oriya',
        0x57: 'Punjabi',   0x58: 'Polish',
        0x59: 'Pashto',    0x5a: 'Portuguese',
        0x5b: 'Quechua',   0x5c: 'Zulu',
        0x5d: 'Kirundi',   0x5e: 'Romanian',
        0x5f: 'Russian',   0x60: 'Kinyarwanda',
        0x61: 'Sanskrit',  0x62: 'Sindhi',
        0x63: 'Sangho',    0x64: 'Serbo-Croatian',
        0x65: 'Sinhalese', 0x66: 'Slovak',
        0x67: 'Slovenian', 0x68: 'Samoan',
        0x69: 'Shona',     0x6a: 'Somali',
        0x6b: 'Albanian',  0x6c: 'Serbian',
        0x6d: 'Siswati',   0x6e: 'Sesotho',
        0x6f: 'Sundanese', 0x70: 'Swedish',
        0x71: 'Swahili',   0x72: 'Tamil',
        0x73: 'Telugu',    0x74: 'Tajik',
        0x75: 'Thai',      0x76: 'Tigrinya',
        0x77: 'Turkmen',   0x78: 'Tagalog',
        0x79: 'Setswana',  0x7a: 'Tonga',
        0x7b: 'Turkish',   0x7c: 'Tsonga',
        0x7d: 'Tatar',     0x7e: 'Twi',
        0x7f: 'Uighur',
        0x81: 'Nauru',     0x82: 'Faeroese',
        0x83: 'Frisian',   0x84: 'Interlingua',
        0x85: 'Volapuk',   0x86: 'Interlingue',
        0x87: 'Inupiak',   0x88: 'Yiddish',
        0x89: 'inuktitut', 0x8a: 'Greenlandic',
        0x8b: 'Latin',     0x8c: 'Rhaeto-Romance'
    }

    return WapWspIdentifiedValue(entry, target, source,
                                 kws, 'language', _ISO_639_TABLE)


# - ref 8.4.2.11 of OMA-WAP-TS-WSP-V1_0-20020920-c
# Accept-ranges-value = (None | Bytes | Token-text)
# -------------------------------------------
def WapWspAcceptRange(entry, target, source, kws, length=0):
    _RANGE = {0x80: 'None', 0x81: 'Bytes'}

    return WapWspIdentifiedValue(entry, target, source, kws, 'range', _RANGE)


# - ref 8.4.2.12 of OMA-WAP-TS-WSP-V1_0-20020920-c
# Age-value = Delta-seconds-value
# -------------------------------------------
def WapWspAge(entry, target, source, kws, length=0):
    return FieldEncParse(entry, target, source, kws, FieldTpDeltaSecond,
                         table=_WSP_HEADER_ASSIGNMENT)


# - ref 8.4.2.13 of OMA-WAP-TS-WSP-V1_0-20020920-c
# Allow-value = well-known-method
# Well-known-method = short-integer (0x40 - 0x7f)
# -------------------------------------------
def WapWspAllow(entry, target, source, kws, length=0):
    # TODO: check the value of well-known-method is in the range
    return FieldEncParse(entry, target, source, kws, FieldTpShortInteger,
                         table=_WSP_HEADER_ASSIGNMENT)


# - ref 8.4.2.14 of OMA-WAP-TS-WSP-V1_0-20020920-c
# Authorization-value = value-length Credentials
# Credentials = (Basic Basic-cookie) | (Authentication-scheme *Auth-param)
# Basic = <Octet 128>
# Basic-cookie = User-id Password
# User-id = Text-string
# Password = Text-string
# Authentication-scheme = Token-text
# Auth-param = Parameter
# -------------------------------------------
def WapWspAuthorization(entry, target, source, kws, length=0):
    offset = kws['offset']

    objectp = _assObject(target, source, kws, table=_WSP_HEADER_ASSIGNMENT)
    length = ord(readWithOffset(target, source, 1, kws))
    objectp.appendChildren(target.newItemObject('length', length,
                           field=_newFieldObject(target, kws, offset)))

    # try Basic-cookie
    basic = ord(readWithOffset(target, source, 1, kws))
    if basic == 0x80:
        if kws['configs'].get(_ENABLE_DETAILS):
            objectp.appendChildren(target.newItemObject('basic', basic,
                                   field=_newFieldObject(target, kws, offset)))
        ret, userid, field = PduFieldParse(FieldTpTextString,
                                           target, source, kws)
        if ret:
            objectp.appendChildren(target.newItemObject('userid',
                                   userid, field=field))
            ret, password, field = PduFieldParse(FieldTpTextString,
                                                 target, source, kws)
            if ret:
                content = 'userid=%s password=%s' % (userid, password)
                objectp.appendChildren(target.newItemObject('password',
                                       password, field=field))
    else:
        # Authentication-scheme
        ret, content, field = PduFieldParse(FieldTpTextString,
                                            target, source, kws)
        if ret:
            objectp.appendChildren(target.newItemObject('scheme',
                                   content, field=field))
            left = length - (kws['offset'] - offset)
            if left > 0:
                ret = WapWspParameter(objectp, target, source, kws, left, True)

    if ret:
        objectp.update(content='authentication=%s' % content,
                       field=_newFieldObject(target, kws, offset))
        entry.appendChildren(objectp)

    return ret


# - ref 8.4.2.15 of OMA-WAP-TS-WSP-V1_0-20020920-c
# Cache-control-value = No-cache | No-store | ...
# Cache-directive = No-cache 1 * (Field-name) ...
# -------------------------------------------
def WapWspCacheControl(entry, target, source, kws, length=0):
    _CONTROL = {
        0x80: 'No-cache',          0x81: 'No-store',
        0x82: 'Max-age',           0x83: 'Max-stale',
        0x84: 'Min-fresh',         0x85: 'Only-if-cached',
        0x86: 'Public',            0x87: 'Private',
        0x88: 'No-transform',      0x89: 'Must-revalidate',
        0x8a: 'Proxy-revalidate',  0x8b: 'S-maxage',
        0x8c: 'Cache-extension'
    }

    return WapWspIdentifiedValue(entry, target, source,
                                 kws, 'control', _CONTROL)


# - ref 8.4.2.16 of OMA-WAP-TS-WSP-V1_0-20020920-c
# Connection-value = (Close | Token-text)
# Close = <Octet 128>
# -------------------------------------------
def WapWspConnection(entry, target, source, kws, length=0):
    _CONNECTION = {0x80: 'Close'}

    def _callback(entry, target, source, kws, value):
        ret, token, field = PduFieldParse(FieldTpTextString,
                                          target, source, kws)
        if ret:
            entry.appendChildren(target.newItemObject('token',
                                 token, field=field))

        return ret

    return WapWspIdentifiedValue(entry, target, source, kws,
                                 'connection', _CONNECTION, _callback)


# - ref 8.4.2.18 of OMA-WAP-TS-WSP-V1_0-20020920-c
# -------------------------------------------
def WapWspContentEncoding(entry, target, source, kws, length=0):
    return WapWspAcceptEncoding(entry, target, source, kws)


# - ref 8.4.2.19 of OMA-WAP-TS-WSP-V1_0-20020920-c
# -------------------------------------------
def WapWspContentLanguage(entry, target, source, kws, length=0):
    return WapWspAcceptLanguage(entry, target, source, kws)


# - ref 8.4.2.20 of OMA-WAP-TS-WSP-V1_0-20020920-c
# Content-length-value = Integer-value
# -------------------------------------------
def WapWspContentLength(entry, target, source, kws, length=0):
    return FieldEncParse(entry, target, source, kws, FieldTpInteger,
                         table=_WSP_HEADER_ASSIGNMENT)


# - ref 8.4.2.21 of OMA-WAP-TS-WSP-V1_0-20020920-c
# Content-location-value = Uri-value
# -------------------------------------------
def WapWspContentLocation(entry, target, source, kws, length=0):
    return FieldEncParse(entry, target, source, kws, FieldTpUriValue,
                         table=_WSP_HEADER_ASSIGNMENT)


# - ref 8.4.2.22 of OMA-WAP-TS-WSP-V1_0-20020920-c
# Content-Md5-value = value-length Digest
# Digest = 16 * 16 OCTECT
# -------------------------------------------
def WapWspContentMd5(entry, target, source, kws, length=0):
    offset = kws['offset']

    objectp = _assObject(target, source, kws, table=_WSP_HEADER_ASSIGNMENT)
    length = ord(readWithOffset(target, source, 1, kws))
    objectp.appendChildren(target.newItemObject('length', length,
                           field=_newFieldObject(target, kws, offset)))
    objectp.appendChildren(
        target.newItemObject(
            'digest',
            field=_newFieldObject(target, kws, offset, size=length)))

    kws['offset'] += length
    objectp.update(content='md5-digest',
                   field=_newFieldObject(target, kws, offset))
    return entry.appendChildren(objectp)


# - ref 8.4.2.23 of OMA-WAP-TS-WSP-V1_0-20020920-c
# Content-range = Value-length First-byte-pos Entity-length
# First-byte-pos = Uintvar-integer
# Entity-length = Unknown-length | Uintvar-integer
# Unknown-length = <Octet 128>
# -------------------------------------------
def WapWspContentRange(entry, target, source, kws, length=0):
    offset = kws['offset']

    objectp = _assObject(target, source, kws, table=_WSP_HEADER_ASSIGNMENT)
    length = ord(readWithOffset(target, source, 1, kws))
    objectp.appendChildren(target.newItemObject('length', length,
                           field=_newFieldObject(target, kws, offset)))
    # TODO: check the content of content-range
    objectp.appendChildren(
        target.newItemObject(
            'content',
            field=_newFieldObject(target, kws, offset, size=length)))

    kws['offset'] += length
    objectp.update(content='content-range',
                   field=_newFieldObject(target, kws, offset))
    return entry.appendChildren(objectp)


# - ref 8.4.2.25 of OMA-WAP-TS-WSP-V1_0-20020920-c
# Date = Date-value
# -------------------------------------------
def WapWspDate(entry, target, source, kws, length=0):
    return FieldEncParse(entry, target, source, kws, FieldTpDateValue,
                         table=_WSP_HEADER_ASSIGNMENT)


# - ref 8.4.2.25 of OMA-WAP-TS-WSP-V1_0-20020920-c
# Etag-value = Text-string
# -------------------------------------------
def WapWspEtag(entry, target, source, kws, length=0):
    return FieldEncParse(entry, target, source, kws, FieldTpTextString,
                         table=_WSP_HEADER_ASSIGNMENT)


# - ref 8.4.2.27 of OMA-WAP-TS-WSP-V1_0-20020920-c
# Expires-value = Date-value
# -------------------------------------------
def WapWspExpires(entry, target, source, kws, length=0):
    return FieldEncParse(entry, target, source, kws, FieldTpDateValue,
                         table=_WSP_HEADER_ASSIGNMENT)


# - ref 8.4.2.28 of OMA-WAP-TS-WSP-V1_0-20020920-c
# From-value = Text-string
# -------------------------------------------
def WapWspFrom(entry, target, source, kws, length=0):
    return FieldEncParse(entry, target, source, kws, FieldTpTextString,
                         table=_WSP_HEADER_ASSIGNMENT)


# - ref 8.4.2.29 of OMA-WAP-TS-WSP-V1_0-20020920-c
# Host-value = Text-string
# -------------------------------------------
def WapWspHost(entry, target, source, kws, length=0):
    return FieldEncParse(entry, target, source, kws, FieldTpTextString,
                         table=_WSP_HEADER_ASSIGNMENT)


# - ref 8.4.2.30 of OMA-WAP-TS-WSP-V1_0-20020920-c
# If-modified-since-value = Text-string
# -------------------------------------------
def WapWspIfModifiedSince(entry, target, source, kws, length=0):
    return FieldEncParse(entry, target, source, kws, FieldTpDateValue,
                         table=_WSP_HEADER_ASSIGNMENT)


# - ref 8.4.2.31 of OMA-WAP-TS-WSP-V1_0-20020920-c
# if-match-value = Text-string
# -------------------------------------------
def WapWspIfMatch(entry, target, source, kws, length=0):
    return FieldEncParse(entry, target, source, kws, FieldTpTextString,
                         table=_WSP_HEADER_ASSIGNMENT)


# - ref 8.4.2.32 of OMA-WAP-TS-WSP-V1_0-20020920-c
# if-none-match-value = Text-string
# -------------------------------------------
def WapWspIfNoneMatch(entry, target, source, kws, length=0):
    return FieldEncParse(entry, target, source, kws, FieldTpTextString,
                         table=_WSP_HEADER_ASSIGNMENT)


# - ref 8.4.2.33 of OMA-WAP-TS-WSP-V1_0-20020920-c
# if-range = Text-string | Date-Value
# -------------------------------------------
def WapWspIfRange(entry, target, source, kws, length=0):
    offset = kws['offset']

    objectp = _assObject(target, source, kws, table=_WSP_HEADER_ASSIGNMENT)
    # try Date-value first
    ret, date, field = PduFieldParse(FieldTpDateValue, target, source, kws)
    if ret:
        objectp.appendChildren(target.newItemObject('date', date, field=field))
    else:
        ret, text, field = PduFieldParse(
            FieldTpTextString, target, source, kws)
        if ret:
            objectp.appendChildren(target.newItemObject(
                                   'text', text, field=field))

    if ret:
        objectp.update(content='if-range',
                       field=_newFieldObject(target, kws, offset))
        entry.appendChildren(objectp)

    return ret


# - ref 8.4.2.34 of OMA-WAP-TS-WSP-V1_0-20020920-c
# If-unmodified-since-value = Text-string
# -------------------------------------------
def WapWspIfUnmodifiedSince(entry, target, source, kws, length=0):
    return FieldEncParse(entry, target, source, kws, FieldTpDateValue,
                         table=_WSP_HEADER_ASSIGNMENT)


# - ref 8.4.2.36 of OMA-WAP-TS-WSP-V1_0-20020920-c
# Location-value = Text-string
# -------------------------------------------
def WapWspLocation(entry, target, source, kws, length=0):
    return FieldEncParse(entry, target, source, kws, FieldTpUriValue,
                         table=_WSP_HEADER_ASSIGNMENT)


# - ref 8.4.2.35 of OMA-WAP-TS-WSP-V1_0-20020920-c
# If-last-modified-value = Text-string
# -------------------------------------------
def WapWspIfLastModified(entry, target, source, kws, length=0):
    return FieldEncParse(entry, target, source, kws, FieldTpDateValue,
                         table=_WSP_HEADER_ASSIGNMENT)


# - ref 8.4.2.37 of OMA-WAP-TS-WSP-V1_0-20020920-c
# Max-forwards-value = integer-value
# -------------------------------------------
def WapWspMaxForwards(entry, target, source, kws, length=0):
    return FieldEncParse(entry, target, source, kws, FieldTpInteger,
                         table=_WSP_HEADER_ASSIGNMENT)


# - ref 8.4.2.38 of OMA-WAP-TS-WSP-V1_0-20020920-c
# Pragma-value = No-cache | (Value-length ...)
# -------------------------------------------
def WapWspPragma(entry, target, source, kws, length=0):
    _PRAGMA = {0x80: 'No-cache'}

    def _callback(entry_, target_, source_, kws_, value):
        ret, length, field = PduFieldParse(FieldTpValueLength,
                                           target_, source_, kws_)
        if ret:
            if kws['configs'].get(_ENABLE_DETAILS):
                entry_.appendChildren('length', length, field=field)
            entry_.appendChildren(
                'content', field=_newFieldObject(target_, kws_, size=length))
            kws['offset'] += length

        return ret

    return WapWspIdentifiedValue(entry, target, source, kws,
                                 'pragma', _PRAGMA, _callback)


# - ref 8.4.2.39 of OMA-WAP-TS-WSP-V1_0-20020920-c
# Proxy-Authenticate-value = Value-length Challenge
# -------------------------------------------
def WapWspProxyAuthenticate(entry, target, source, kws, length=0):
    return FieldEncParse(entry, target, source, kws, FieldTpValueLength,
                         table=_WSP_HEADER_ASSIGNMENT)


# - ref 8.4.2.40 of OMA-WAP-TS-WSP-V1_0-20020920-c
# Proxy-Authorization-value = Value-length Credentials
# -------------------------------------------
def WapWspProxyAuthorization(entry, target, source, kws, length=0):
    offset = kws['offset']

    objectp = _assObject(target, source, kws, table=_WSP_HEADER_ASSIGNMENT)
    ret, length, field = PduFieldParse(FieldTpValueLength, target, source, kws)
    if ret:
        objectp.appendChildren(
            target.newItemObject(
                'length',
                length, field=_newFieldObject(target, kws, offset)))
        objectp.appendChildren(
            target.newItemObject(
                'credentials',
                field=_newFieldObject(target, kws, offset, size=length)))
        kws['offset'] += length

        objectp.update(content='proxy-authorization',
                       field=_newFieldObject(target, kws, offset))
        entry.appendChildren(objectp)

    return ret


# - ref 8.4.2.41 of OMA-WAP-TS-WSP-V1_0-20020920-c
# Public-value = (well-known-method | Token-text)
# Well-known-method = short-integer (0x40 - 0x7f)
# -------------------------------------------
def WapWspPublic(entry, target, source, kws, length=0):
    offset = kws['offset']

    objectp = _assObject(target, source, kws, table=_WSP_HEADER_ASSIGNMENT)
    ret, method, field = PduFieldParse(
        FieldTpShortInteger, target, source, kws)
    if ret and 0x40 <= method <= 0x7f:
        objectp.appendChildren(target.newItemObject('method', method,
                                                    field=field))
    else:
        ret, text, field = PduFieldParse(
            FieldTpTextString, target, source, kws)
        if ret:
            objectp.appendChildren(target.newItemObject('text', text,
                                                        field=field))

    if ret:
        objectp.update(content='public-value',
                       field=_newFieldObject(target, kws, offset))
        entry.appendChildren(objectp)

    return ret


# - ref 8.4.2.42 of OMA-WAP-TS-WSP-V1_0-20020920-c
# Range-value = Value-Length (Byte-range-spec ...)
# -------------------------------------------
def WapWspRange(entry, target, source, kws, length=0):
    offset = kws['offset']

    objectp = _assObject(target, source, kws, table=_WSP_HEADER_ASSIGNMENT)
    ret, length, _ = PduFieldParse(FieldTpInteger, target, source, kws)
    if ret:
        objectp.appendChildren(target.newItemObject('length', length,
                               field=_newFieldObject(target, kws, offset)))
        # TODO: check the content of byte-range-spec
        objectp.appendChildren(
            target.newItemObject(
                'byte-range-spec',
                field=_newFieldObject(target, kws, offset, size=length)))

        kws['offset'] += length
        objectp.update(content='range-value',
                       field=_newFieldObject(target, kws, offset))
        entry.appendChildren(objectp)

    return ret


# - ref 8.4.2.43 of OMA-WAP-TS-WSP-V1_0-20020920-c
# Referer-value = Uri-value
# -------------------------------------------
def WapWspReferer(entry, target, source, kws, length=0):
    return FieldEncParse(entry, target, source, kws, FieldTpUriValue,
                         table=_WSP_HEADER_ASSIGNMENT)


# - ref 8.4.2.44 of OMA-WAP-TS-WSP-V1_0-20020920-c
# Retry-after-value = Value-length (Retry-date-value | Retry-delta-seconds)
# Retry-date-value = Absolute-time Date-Value
# Retry-delta-seconds = Relative-time Delta-seconds
# Absolute-time = <Octet 128>
# Relative-time = <Octect 129>
# -------------------------------------------
def WapWspRetryAfter(entry, target, source, kws, length=0):
    offset = kws['offset']

    objectp = _assObject(target, source, kws, table=_WSP_HEADER_ASSIGNMENT)
    length = ord(readWithOffset(target, source, 1, kws))
    objectp.appendChildren(target.newItemObject('length', length,
                           field=_newFieldObject(target, kws, offset)))
    absolute = ord(readWithOffset(target, source, 1, kws))
    if kws['configs'].get(_ENABLE_DETAILS):
        objectp.appendChildren(
            target.newItemObject(
                'tag', absolute,
                field=_newFieldObject(target, kws, size=1)))

    if absolute == 0x80:
        ret, value, field = PduFieldParse(
            FieldTpDateValue, target, source, kws)
        if ret:
            objectp.appendChildren(target.newItemObject('time',
                                   value, field=field))
    else:
        ret, seconds, field = PduFieldParse(FieldTpLongInteger,
                                            target, source, kws)
        if ret:
            objectp.appendChildren(
                target.newItemObject('second', seconds, kws))

    if ret:
        objectp.update(content='retry-after',
                       field=_newFieldObject(target, kws, offset))
        entry.appendChildren(objectp)

    return ret


# - ref 8.4.2.45 of OMA-WAP-TS-WSP-V1_0-20020920-c
# Server-value = Text-string
# -------------------------------------------
def WapWspServer(entry, target, source, kws, length=0):
    return FieldEncParse(entry, target, source, kws, FieldTpTextString,
                         table=_WSP_HEADER_ASSIGNMENT)


# - ref 8.4.2.46 of OMA-WAP-TS-WSP-V1_0-20020920-c
# Transfer-encoding-values = Chunked | Token-text
# -------------------------------------------
def WapWspTransferEncoding(entry, target, source, kws, length=0):
    _ENCODING = {0x80: 'Chunked'}

    def _callback(entry, target, source, kws, value):
        ret, token, field = PduFieldParse(FieldTpTextString,
                                          target, source, kws)
        if ret:
            entry.append('encoding', token, field=field)

        return ret

    return WapWspIdentifiedValue(entry, target, source, kws,
                                 'encoding', _ENCODING, _callback)


# - ref 8.4.2.47 of OMA-WAP-TS-WSP-V1_0-20020920-c
# Upgrade-value = Text-string
# -------------------------------------------
def WapWspUpgrade(entry, target, source, kws, length=0):
    return FieldEncParse(entry, target, source, kws, FieldTpTextString,
                         table=_WSP_HEADER_ASSIGNMENT)


# - ref 8.4.2.48 of OMA-WAP-TS-WSP-V1_0-20020920-c
# user-agent-value = Text-string
# -------------------------------------------
def WapWspUserAgent(entry, target, source, kws, length=0):
    return FieldEncParse(entry, target, source, kws, FieldTpTextString,
                         table=_WSP_HEADER_ASSIGNMENT)


# - refer 8.4.49 of OMA-WAP-TS-WSP-V1_0-20020920-c
# vary-value = Field-name
# Field-name = Token-text | Well-known-field-name
# Well-known-field-name = Short-integer
# -------------------------------------------
def WapWspVary(entry, target, source, kws, length=0):
    offset = kws['offset']

    objectp = _assObject(target, source, kws, table=_WSP_HEADER_ASSIGNMENT)
    ret, wellknown, field = PduFieldParse(FieldTpShortInteger,
                                          target, source, kws)
    if ret:
        objectp.appendChildren(target.newItemObject('value',
                               wellknown, field=field))
    else:
        ret, text, field = PduFieldParse(
            FieldTpTextString, target, source, kws)
        if ret:
            objectp.appendChildren(target.newItemObject('value',
                                   text, field=field))

    if ret:
        objectp.update(content='vary-value',
                       field=_newFieldObject(target, kws, offset))
        entry.appendChildren(objectp)

    return ret


# - ref 8.4.2.50 of OMA-WAP-TS-WSP-V1_0-20020920-c
# Via-value = Text-string
# -------------------------------------------
def WapWspUserVia(entry, target, source, kws, length=0):
    return FieldEncParse(entry, target, source, kws, FieldTpTextString,
                         table=_WSP_HEADER_ASSIGNMENT)


# - ref 8.4.2.51 of OMA-WAP-TS-WSP-V1_0-20020920-c
# Warning = Warning-code | Warning-Value
# Warning-value = Value-length Warning-code Warn-agent Warn-agent
# Warn-code = Short-integer
# Warn-agent = Text-string
# Warn-Text = Text-string
# -------------------------------------------
def WapWspWarning(entry, target, source, kws, length=0):
    offset = kws['offset']

    objectp = _assObject(target, source, kws, table=_WSP_HEADER_ASSIGNMENT)
    ret, code, field = PduFieldParse(FieldTpShortInteger, target, source, kws)
    if ret:
        objectp.appendChildren(target.newItemObject('code', code, field=field))
    else:
        ret, length, field = PduFieldParse(FieldTpInteger, target, source, kws)
        if kws['configs'].get(_ENABLE_DETAILS):
            objectp.appendChildren(target.newItemObject('length',
                                   length, field=field))
        if ret:
            ret, code, field = PduFieldParse(FieldTpShortInteger,
                                             target, source, kws)
            if ret:
                objectp.appendChildren(target.newItemObject('code',
                                       code, field=field))
                ret, agent, field = PduFieldParse(FieldTpTextString,
                                                  target, source, kws)
            if ret:
                objectp.appendChildren(target.newItemObject('agent',
                                       agent, field=field))
                ret, text, field = PduFieldParse(FieldTpTextString,
                                                 target, source, kws)
            if ret:
                objectp.appendChildren(target.newItemObject('text',
                                       text, field=field))

    if ret:
        objectp.update(content='warning',
                       field=_newFieldObject(target, kws, offset))
        entry.appendChildren(objectp)

    return ret


# - ref 8.4.2.52 of OMA-WAP-TS-WSP-V1_0-20020920-c
# WWW-authenticate-value = Value-length Challenge
# -------------------------------------------
def WapWspWwwAuthenticate(entry, target, source, kws, length=0):
    return WapWspProxyAuthenticate(entry, target, source, kws)


# - ref 8.4.2.53 of OMA-WAP-TS-WSP-V1_0-20020920-c
# Content-disposition-value = Value-length Disposition *(Parameter)
# Disposition = Form-data | Attachment | Inline | Token-Text
# Form-data = <Octet 128>
# Attachment = <Octet 129>
# Inline = <Octet 130>
# -------------------------------------------
def WapWspContentDisposition(entry, target, source, kws, length=0):
    offset = kws['offset']
    _DISPOSITION = {0x80: 'Form-data', 0x81: 'Attachment', 0x82: 'Inline'}

    objectp = _assObject(target, source, kws, table=_WSP_HEADER_ASSIGNMENT)
    ret, length, field = PduFieldParse(FieldTpValueLength, target, source, kws)
    if ret:
        if kws['configs'].get(_ENABLE_DETAILS):
            objectp.appendChildren(target.newItemObject('length',
                                   length, field=field))
        disposition = ord(readWithOffset(target, source, 1, kws))

        if disposition in _DISPOSITION:
            text = '%s (%02x)' % (_DISPOSITION.get(disposition), disposition)
            objectp.appendChildren(target.newItemObject('disposition', text,
                                   field=_newFieldObject(target, kws, size=1)))
        else:
            kws['offset'] -= 1
            ret, disposition, field = PduFieldParse(FieldTpTextString,
                                                    target, source, kws)
            if ret:
                objectp.appendChildren(
                    target.newItemObject(
                        'disposition', disposition, field=field))

        left = length - (kws['offset'] - offset)
        if ret and left > 0:
            ret = WapWspParameter(objectp, target, source, kws, left, True)

    if ret:
        objectp.update(
            content=text, field=_newFieldObject(target, kws, offset))
        entry.appendChildren(objectp)

    return ret


# - ref 8.4.2.54 of OMA-WAP-TS-WSP-V1_0-20020920-c
# Application-id-value = Uri-value | App-assigned-code
# App-assigned-code = Integer-Value
# -------------------------------------------
def WapWspApplicationId(entry, target, source, kws, length=0):
    offset = kws['offset']

    objectp = _assObject(target, source, kws, table=_WSP_HEADER_ASSIGNMENT)
    # try App-assigned-code
    ret, code, field = PduFieldParse(FieldTpInteger, target, source, kws)
    if ret:
        objectp.appendChildren(target.newItemObject('code', code, field=field))
    else:
        ret, uri, field = PduFieldParse(FieldTpUriValue, target, source, kws)
        if ret:
            objectp.appendChildren(target.newItemObject('uri',
                                   uri, field=field))

    if ret:
        objectp.update(content='application-id',
                       field=_newFieldObject(target, kws, offset))
        entry.appendChildren(objectp)

    return ret


# - ref 8.4.2.55 of OMA-WAP-TS-WSP-V1_0-20020920-c
# content-uri-value = Uri-Value
# -------------------------------------------
def WapWspContentUri(entry, target, source, kws, length=0):
    return FieldEncParse(entry, target, source, kws, FieldTpUriValue,
                         table=_WSP_HEADER_ASSIGNMENT)


# - ref 8.4.2.55 of OMA-WAP-TS-WSP-V1_0-20020920-c
# initiator-uri-value = Uri-Value
# -------------------------------------------
def WapWspInitiatorUri(entry, target, source, kws, length=0):
    return FieldEncParse(entry, target, source, kws, FieldTpUriValue,
                         table=_WSP_HEADER_ASSIGNMENT)


# - ref 8.4.2.57 of OMA-WAP-TS-WSP-V1_0-20020920-c
# Accept-application-value = Any-application | Application-id-Value
# Any-application = <Octet 128>
# -------------------------------------------
def WapWspAcceptApplication(entry, target, source, kws, length=0):
    offset = kws['offset']

    objectp = _assObject(target, source, kws, table=_WSP_HEADER_ASSIGNMENT)
    # try App-assigned-code
    application = ord(readWithOffset(target, source, 1, kws))
    if application == 0x80:
        objectp.appendChildren(
            target.newItemObject(
                'application',
                'Any Application', field=_newFieldObject(target, kws, size=1)))
        objectp.update(content=application,
                       field=_newFieldObject(target, kws, offset))
        entry.appendChildren(objectp)
    else:
        ret = WapWspApplicationId(entry, target, source, kws)

    return ret


# - ref 8.4.2.58 of OMA-WAP-TS-WSP-V1_0-20020920-c
# Bearer-indication-value = Integer-value
# -------------------------------------------
def WapWspBearerIndication(entry, target, source, kws, length=0):
    return FieldEncParse(entry, target, source, kws, FieldTpInteger,
                         table=_WSP_HEADER_ASSIGNMENT)


# - ref 8.4.2.59 of OMA-WAP-TS-WSP-V1_0-20020920-c
# Push-flag-value = Short-integer
# -------------------------------------------
def WapWspPushFlag(entry, target, source, kws, length=0):
    return FieldEncParse(entry, target, source, kws, FieldTpShortInteger,
                         table=_WSP_HEADER_ASSIGNMENT)


# - ref 8.4.2.60 of OMA-WAP-TS-WSP-V1_0-20020920-c
# Profile-value = Uri-value
# -------------------------------------------
def WapWspProfile(entry, target, source, kws, length=0):
    return FieldEncParse(entry, target, source, kws, FieldTpUriValue,
                         table=_WSP_HEADER_ASSIGNMENT)


# - ref 8.4.2.61 of OMA-WAP-TS-WSP-V1_0-20020920-c
# Profile-diff-value = Value-Length CCPP-Profile
# CCPP-profile = *OCTET ; WBXML
# -------------------------------------------
def WapWspProfileDiff(entry, target, source, kws, length=0):
    offset = kws['offset']

    objectp = _assObject(target, source, kws, table=_WSP_HEADER_ASSIGNMENT)
    ret, length, field = PduFieldParse(FieldTpInteger, target, source, kws)
    if ret:
        objectp.appendChildren(target.newItemObject('length',
                               length, field=field))
        # TODO: check the content of content-range
        objectp.appendChildren(
            target.newItemObject(
                'profile',
                field=_newFieldObject(target, kws, offset, size=length)))

        kws['offset'] += length
        objectp.update(field=_newFieldObject(target, kws, offset))
        entry.appendChildren(objectp)

    return ret


# - ref 8.4.2.62 of OMA-WAP-TS-WSP-V1_0-20020920-c
# Profile-warning-value = Warn-code | (Value-length Warning code ...)
# -------------------------------------------
def WapWspProfileWarning(entry, target, source, kws, length=0):
    _PROFILE = {
        0x10: '100', 0x11: '101', 0x12: '102',
        0x20: '200', 0x21: '201', 0x22: '202', 0x23: '203'
    }

    def _callback(entry, target, source, kws, value):
        ret, length, field = PduFieldParse(FieldTpInteger, target, source, kws)
        if ret:
            if kws['configs'].get(_ENABLE_DETAILS):
                entry.appendChildren('length', length, field=field)
            # TODO: ...
            entry.appendChildren(
                'warning', field=_newFieldObject(target, kws, size=length))

        return ret

    return WapWspIdentifiedValue(entry, target, source, kws,
                                 'warning', _PROFILE, _callback)


# - ref 8.4.2.63 of OMA-WAP-TS-WSP-V1_0-20020920-c
# Expect-value = 100-continue | Expect-expression
# Expect-expression = value-length Expression-var express-Value
# Expression-var = token-Text
# Expression-value = (token-text | Quoted-string) *expect-params
# Expect-params = Token-text Expect-param-Value
# Expect-param-value = Text-Value
# 100-continue = <Octet 128>
# -------------------------------------------
def WapWspExpect(entry, target, source, kws, length=0):
    _EXPECT = {0x80: '<100-continue>'}

    def _callback(entry, target, source, kws, value):
        ret, length, field = PduFieldParse(FieldTpInteger, target, source, kws)
        if ret:
            if kws['configs'].get(_ENABLE_DETAILS):
                entry.appendChildren('length', length, field=field)
            # TODO: ...
            entry.appendChildren(
                'expect', field=_newFieldObject(target, kws, size=length))

        return ret

    return WapWspIdentifiedValue(entry, target, source, kws,
                                 'expect', _EXPECT, _callback)


# - ref 8.4.2.64 of OMA-WAP-TS-WSP-V1_0-20020920-c
# TE-value = Trailers | TE-General-Form
# TE-General-Form = Value-length (Well-known-TE ...)
# -------------------------------------------
def WapWspTE(entry, target, source, kws, length=0):
    _TE = {0x81: 'Trailers'}

    def _callback(entry, target, source, kws, value):
        ret, length, field = PduFieldParse(FieldTpInteger, target, source, kws)
        if ret:
            if kws['configs'].get(_ENABLE_DETAILS):
                entry.appendChildren('length', length, field=field)
            # TODO: ...
            entry.appendChildren(
                'expect', field=_newFieldObject(target, kws, size=length))

        return ret

    return WapWspIdentifiedValue(entry, target, source, kws,
                                 'expect', _TE, _callback)


# - ref 8.4.2.65 of OMA-WAP-TS-WSP-V1_0-20020920-c
# Trailer-value = (Well-known-header-field | Token-text)
# Well-known-header-field = integer-value
# -------------------------------------------
def WapWspTrailer(entry, target, source, kws, length=0):
    offset = kws['offset']

    objectp = _assObject(target, source, kws, table=_WSP_HEADER_ASSIGNMENT)
    # try Well-known-header-field
    ret, value, field = PduFieldParse(FieldTpInteger, target, source, kws)
    if ret:
        objectp.appendChildren(target.newItemObject('value', value,
                                                    field=field))
    else:
        ret, value, field = PduFieldParse(FieldTpTextString,
                                          target, source, kws)
        if ret:
            objectp.appendChildren(target.newItemObject('value', value,
                                                        field=field))

    if ret:
        objectp.update(content=value,
                       field=_newFieldObject(target, kws, offset))
        entry.appendChildren(objectp)

    return ret


# - ref 8.4.2.66 of OMA-WAP-TS-WSP-V1_0-20020920-c
# X-Wap-Tod-value = Date-value
# -------------------------------------------
def WapWspTod(entry, target, source, kws, length=0):
    return FieldEncParse(entry, target, source, kws, FieldTpDateValue,
                         table=_WSP_HEADER_ASSIGNMENT)


# - ref 8.4.2.67 of OMA-WAP-TS-WSP-V1_0-20020920-c
# Content-ID-value = Quoted-string
# -------------------------------------------
def WapWspContentId(entry, target, source, kws, length=0):
    return FieldEncParse(entry, target, source, kws, FieldTpTextString,
                         table=_WSP_HEADER_ASSIGNMENT)


# - ref 8.4.2.68 of OMA-WAP-TS-WSP-V1_0-20020920-c
# Set-Cookie-value = value-length Cookie-version cookie-name
#                    cookie-value *Parameter
# cookie-version = version-Value
# cookie-name = text-string
# cookie-val = text-string
# -------------------------------------------
def WapWspSetCookie(entry, target, source, kws, length=0):
    offset = kws['offset']

    objectp = _assObject(target, source, kws, table=_WSP_HEADER_ASSIGNMENT)
    ret, length, field = PduFieldParse(FieldTpInteger, target, source, kws)
    if ret:
        objectp.appendChildren(target.newItemObject('length',
                               length, field=field))
        # TODO:...
        objectp.appendChildren(
            target.newItemObject(
                'profile',
                field=_newFieldObject(target, kws, offset, size=length)))

        kws['offset'] += length
        objectp.update(field=_newFieldObject(target, kws, offset))
        entry.appendChildren(objectp)

    return ret


# - ref 8.4.2.69 of OMA-WAP-TS-WSP-V1_0-20020920-c
# cookie-value = Value-length Cookie-version *cookie
# Cookie = cookie-length cookie-name cookie-value [Cookie-parameters]
# cookie-length = Uintvar-integer
# cookie-parameters = path [domain]
# path = text-string
# domain = text-string
# -------------------------------------------
def WapWspCookie(entry, target, source, kws, length=0):
    offset = kws['offset']

    objectp = _assObject(target, source, kws, table=_WSP_HEADER_ASSIGNMENT)
    ret, length, field = PduFieldParse(FieldTpInteger, target, source, kws)
    if ret:
        objectp.appendChildren(target.newItemObject('length',
                               length, field=field))
        # TODO:...
        objectp.appendChildren(
            target.newItemObject(
                'profile',
                field=_newFieldObject(target, kws, offset, size=length)))

        kws['offset'] += length
        objectp.update(field=_newFieldObject(target, kws, offset))
        entry.appendChildren(objectp)

    return ret


# - ref 8.4.2.70 of OMA-WAP-TS-WSP-V1_0-20020920-c
# Encoding-version-value = Version-value
#                          | Value-length Code-page [Version-value]
# code-page = short-integer
# -------------------------------------------
def WapWspEncodingVersion(entry, target, source, kws, length=0):
    offset = kws['offset']

    objectp = _assObject(target, source, kws, table=_WSP_HEADER_ASSIGNMENT)
    # try Version-value
    ret, version, field = PduFieldParse(FieldTpVersionValue,
                                        target, source, kws)
    if ret:
        objectp.appendChildren(target.newItemObject('version',
                               version, field=field))
    else:
        ret, length, field = PduFieldParse(FieldTpInteger, target, source, kws)
        if ret:
            objectp.appendChildren(target.newItemObject('length',
                                   length, field=field))
            ret, codepage, field = PduFieldParse(FieldTpShortInteger,
                                                 target, source, kws)
        if ret:
            objectp.appendChildren(target.newItemObject('code-page',
                                   codepage, field=field))
            if length >= kws['offset'] - offset:
                ret, version, field = PduFieldParse(FieldTpVersionValue,
                                                    target, source, kws)
                if ret:
                    objectp.appendChildren(target.newItemObject('version',
                                           version, field=field))

    if ret:
        objectp.update(content=version,
                       field=_newFieldObject(target, kws, offset))
        entry.appendChildren(objectp)

    return ret


# - ref 8.4.2.71 of OMA-WAP-TS-WSP-V1_0-20020920-c
# X-WAP-Security-value = Close-subordinate
# Close-subordinate = <Octet 128>
# -------------------------------------------
def WapWspSecurity(entry, target, source, kws, length=0):
    _SUBORDINATE = {0x80: 'close'}

    return WapWspIdentifiedValue(entry, target, source, kws,
                                 'subordinate', _SUBORDINATE)


# - ref 8.4.2.72 of OMA-WAP-TS-WSP-V1_0-20020920-c
# X-Wap-Loc-Invocation-value = value-length Loc-Invocation-document
# Loc-Invocation-document = 1* OCTET ; WBXML form
# -------------------------------------------
def WapWspLocInvocation(entry, target, source, kws, length=0):
    offset = kws['offset']

    objectp = _assObject(target, source, kws, table=_WSP_HEADER_ASSIGNMENT)
    ret, length, field = PduFieldParse(FieldTpInteger, target, source, kws)
    if ret:
        objectp.appendChildren(target.newItemObject('length',
                               length, field=field))
        # TODO: ...
        objectp.appendChildren(
            target.newItemObject(
                'invocation',
                field=_newFieldObject(target, kws, offset, size=length)))

        kws['offset'] += length
        objectp.update(field=_newFieldObject(target, kws, offset))
        entry.appendChildren(objectp)

    return ret


# - ref 8.4.2.73 of OMA-WAP-TS-WSP-V1_0-20020920-c
# X-Wap-Loc-Delivery-value = value-length Loc-Invocation-document
# Loc-Delivery-document = 1* OCTET ; WBXML form
# -------------------------------------------
def WapWspLocDelivery(entry, target, source, kws, length=0):
    offset = kws['offset']

    objectp = _assObject(target, source, kws, table=_WSP_HEADER_ASSIGNMENT)
    ret, length, field = PduFieldParse(FieldTpInteger, target, source, kws)
    if ret:
        objectp.appendChildren(target.newItemObject('length',
                               length, field=field))
        # TODO: ...
        objectp.appendChildren(
            target.newItemObject(
                'invocation',
                field=_newFieldObject(target, kws, offset, size=length)))

        kws['offset'] += length
        objectp.update(field=_newFieldObject(target, kws, offset))
        entry.appendChildren(objectp)

    return ret


# - ref 8.3.2.3 of OMA-WAP-TS-WSP-V1_0-20020920-c
# -------------------------------------------
def WapWspCapabilityProtocolOptions(entry, target, source, kws, length=0):
    _FLAG = {
        0x80: 'Confirmed Push Facility',
        0x40: 'Push Facility',
        0x20: 'Session Resume Facility',
        0x10: 'Acknowledgement headers',
        0x08: 'Large Data Transfer'
    }

    li = list()
    offset = kws['offset']

    objectp = _assObject(target, source, kws, table=_WSP_HEADER_ASSIGNMENT)
    # FIXME: the algorithm is possible wrong
    options = ord(readWithOffset(target, source, 1, kws))
    for f in _FLAG:
        if f & options:
            li.append(_FLAG[f])
    objectp.appendChildren(
        target.newItemObject(
            'flag', ';'.join(li),
            field=_newFieldObject(target, kws, offset)))
    entry.appendChildren(objectp)

    return True


# - Table 39, OMA-WAP-TS-WSP-V1_0-20020920-C
# - ref 8.3.2.2 of OMA-WAP-TS-WSP-V1_0-20020920-c
# -------------------------------------------
def WapWspServerSduSize(entry, target, source, kws, length=0):
    offset = kws['offset']

    objectp = _assObject(target, source, kws, table=_WSP_HEADER_ASSIGNMENT)
    ret, value, field = PduFieldParse(FieldTpUintVar, target, source, kws)
    if ret:
        objectp.appendChildren(
            target.newItemObject('size', value, field=field))
        entry.appendChildren(objectp)

    return ret


#  Table 38 of OMA-WAP-TS-WSP-V1_0-20020920-c
# -------------------------------------------
_WSP_CAPABILITY = {
    0x00: ('Client-SDU-Size', WapWspServerSduSize),
    0x01: ('Server-SDU-Size', WapWspServerSduSize),
    0x02: ('Protocol Options', WapWspCapabilityProtocolOptions),
    0x03: ('Method-MOR', None),
    0x04: ('Push-MOR', None),
    0x05: ('Extended Methods', None),
    0x06: ('Header Code pages', None),
    0x07: ('Alias', None),
    0x08: ('Client-Message-Size', None),
    0x09: ('Server-Message-Size', None)
}

# Table 39, OMA-WAP-TS-WSP-V1_0-20020920-C
# -------------------------------------------
_WSP_HEADER_ASSIGNMENT = {
    0x00: ('Accept', WapWspAccept),
    0x01: ('Accept-Charset', WapWspAcceptCharset),
    0x02: ('Accept-Encoding', WapWspAcceptEncoding),
    0x03: ('Accept-Language', WapWspAcceptLanguage),
    0x04: ('Accept-Ranges', WapWspAcceptRange),
    0x05: ('Age', WapWspAge),
    0x06: ('Allow', WapWspAllow),
    0x07: ('Authorization', WapWspAuthorization),
    0x08: ('Cache-Control', WapWspCacheControl),
    0x09: ('Connection', WapWspConnection),
    0x0a: ('Content-Base', None),
    0x0b: ('Content-Encoding', WapWspContentEncoding),
    0x0c: ('Content-Language', WapWspContentLanguage),
    0x0d: ('Content-Length', WapWspContentLength),
    0x0e: ('Content-Location', WapWspContentLocation),
    0x0f: ('Content-MD5', WapWspContentMd5),
    0x10: ('Content-Range', WapWspContentRange),
    0x11: ('Content-Type', WapWspContentType),
    0x12: ('Date', WapWspDate),
    0x13: ('Etag', WapWspEtag),
    0x14: ('Expires', WapWspExpires),
    0x15: ('From', WapWspFrom),
    0x16: ('Host', WapWspHost),
    0x17: ('If-Modified-Since', WapWspIfModifiedSince),
    0x18: ('If-Match', WapWspIfMatch),
    0x19: ('If-None-Match', WapWspIfNoneMatch),
    0x1a: ('If-Range', WapWspIfRange),
    0x1b: ('If-Unmodified-Since', WapWspIfUnmodifiedSince),
    0x1c: ('Location', WapWspLocation),
    0x1d: ('Last-Modified', WapWspIfLastModified),
    0x1e: ('Max-Forwards', WapWspMaxForwards),
    0x1f: ('Pragma', WapWspPragma),
    0x20: ('Proxy-Authenticate', WapWspProxyAuthenticate),
    0x21: ('Proxy-Authoriation', WapWspProxyAuthorization),
    0x22: ('Public', WapWspPublic),
    0x23: ('Range', WapWspRange),
    0x24: ('Referer', WapWspReferer),
    0x25: ('Retry-After', WapWspRetryAfter),
    0x26: ('Server', WapWspServer),
    0x27: ('Transfer-Encoding', WapWspTransferEncoding),
    0x28: ('Upgrade', WapWspUpgrade),
    0x29: ('User-Agent', WapWspUserAgent),
    0x2a: ('Vary', WapWspVary),
    0x2b: ('Via', WapWspUserVia),
    0x2c: ('Warning', WapWspWarning),
    0x2d: ('WWW-Authenticate', WapWspWwwAuthenticate),
    0x2e: ('Content-Disposition', WapWspContentDisposition),
    0x2f: ('X-Wap-Application-Id', WapWspApplicationId),
    0x30: ('X-Wap-Content-URI', WapWspContentUri),
    0x31: ('X-Wap-Initiator-URI', WapWspInitiatorUri),
    0x32: ('Accept-Application', WapWspAcceptApplication),
    0x33: ('Bearer-Indication', WapWspBearerIndication),
    0x34: ('Push-Flag', WapWspPushFlag),
    0x35: ('Profile', WapWspProfile),
    0x36: ('Profile-Diff', WapWspProfileDiff),
    0x37: ('Profile-Warning', WapWspProfileWarning),
    0x38: ('Expect', WapWspExpect),
    0x39: ('TE', WapWspTE),
    0x3a: ('Trailer', WapWspTrailer),
    0x3b: ('Accept-Charset', WapWspAcceptCharset),
    0x3c: ('Accept-Encoding', WapWspAcceptEncoding),
    0x3d: ('Cache-Control', WapWspCacheControl),
    0x3e: ('Content-Range', WapWspContentRange),
    0x3f: ('X-Wap-Tod', WapWspTod),
    0x40: ('Content-ID', WapWspContentId),
    0x41: ('Set-Cookie', WapWspSetCookie),
    0x42: ('Cookie', WapWspCookie),
    0x43: ('Encoding-Version', WapWspEncodingVersion),
    0x44: ('Profile-Warning', WapWspProfileWarning),
    0x45: ('Content-Disposition', WapWspContentDisposition),
    0x46: ('X-WAP-Security', WapWspSecurity),
    0x47: ('Cache-Control', WapWspCacheControl),
    0x48: ('Expect', WapWspExpect),
    0x49: ('X-Wap-Loc-Invocation', WapWspLocInvocation),
    0x4a: ('X-Wap-Loc-Delivery', WapWspLocDelivery)
}


# - ref 8.4 of OMA-WAP-TS-WSP-V1_0-20020920-c
# -------------------------------------------
def WspHandlePduHeader(entry, target, source, kws, length=0, table=None):
    # refer to Table 39
    if table is None:
        table = _WSP_HEADER_ASSIGNMENT

    offset, ret = kws['offset'], True
    while ret and kws['offset'] - offset < length:
        ass = ord(target.readFile(source, 1, **kws))
        if kws['configs'].get(_ENABLE_DEBUG):
            print 'WspHandlePduHeader [%x]' % ass
        if ass & 0x80:
            ass &= 0x7f
            if ass in table:
                _, func = table[ass]
                if func:
                    ret = func(entry, target, source, kws, length)
            else:
                ret = False
        else:
            # treat it as a field with name
            ret, text, field = PduFieldParse(FieldTpTextString,
                                             target, source, kws)
            if ret:
                # treat it as a value
                ret, value, field2 = PduFieldParse(FieldTpValueLength,
                                                   target, source, kws)
                if not ret:
                    ret, value, field2 = PduFieldParse(FieldTpTextString,
                                                       target, source, kws)

                if ret:
                    objectp = target.newItemObject(text, value,
                                                   field=field + field2)
                    objectp.appendChildren(target.newItemObject('text',
                                           text, field=field))
                    objectp.appendChildren(target.newItemObject('value',
                                           value, field=field2))
                    entry.appendChildren(objectp)

    return ret


# - ref 8.5 of OMA-WAP-TS-WSP-V1_0-20020920-c
# -------------------------------------------
def WspDecodeMultipartData(entry, target, source, notify, kws):
    offset = kws['offset']
    # Application/vnd.wap.multipart = Header Entires
    ret, num, field = PduFieldParse(FieldTpUintVar, target, source, kws)
    if kws['configs'].get(_ENABLE_DEBUG):
        print 'Multipart: ', num

    objectp = target.newItemObject('Multiple body')
    if kws['configs'].get(_ENABLE_DETAILS):
        objectp.appendChildren(target.newItemObject('multipart',
                               num, field=field))
    for k in range(num):
        koff = kws['offset']
        obj = target.newItemObject('Part %d' % (k + 1))

        # HeadersLen
        _, length, field = PduFieldParse(FieldTpUintVar, target, source, kws)
        if kws['configs'].get(_ENABLE_DETAILS):
            obj.appendChildren(
                target.newItemObject('header length', length, field=field))
        # DataLen
        _, size, field = PduFieldParse(FieldTpUintVar, target, source, kws)
        if kws['configs'].get(_ENABLE_DETAILS):
            obj.appendChildren(
                target.newItemObject('data length', size, field=field))

        diff = kws['offset'] - koff
        # ContentType
        ret, contents = WapWspContentType(obj, target, source, kws, wsp=True)
        consumed = kws['offset'] - koff - diff
        if ret and consumed != length:
            left = length - consumed
            ob = target.newItemObject(
                'Header', field=_newFieldObject(target, kws, size=left))
            ret = WspHandlePduHeader(ob, target, source, kws, length=left)
            if ret:
                obj.appendChildren(ob)

        if size > 0:
            _TYPE_WITHOUT_UPDATE = {
                'application/smil':
                'Synchronized Multimedia Integration Language',
                'text/plain': 'Text'
            }

            for tp, v in _TYPE_WITHOUT_UPDATE.items():
                if tp in contents:
                    subject = v
                    attribute = 'extractable,no_update'
                    break
            else:
                subject, attribute = 'Data', 'extractable'

            obj.appendChildren(
                target.newItemObject(
                    subject, '',
                    attribute=attribute,
                    field=_newFieldObject(target, kws,
                                          start=kws['start'] + koff + length
                                          + diff, size=size)))
            kws['offset'] = koff + length + size + diff
            obj.update(field=_newFieldObject(target, kws, koff))

        objectp.appendChildren(obj)

    if ret:
        objectp.update(field=_newFieldObject(target, kws, offset))
        entry.appendChildren(objectp)

    return ret


# - ref 8.2.4.1 of OMA-WAP-TS-WSP-V1_0-20020920-c
# -------------------------------------------
def WapWspDecodeConnectPdu(entry, target, source, kws):
    offset = kws['offset']
    version = ord(readWithOffset(target, source, 1, kws))
    entry.append(target.newItemObject('Version', version,
                 field=_newFieldObject(target, kws, 1)))
    _, length, field = PduFieldParse(FieldTpUintVar, target, source, kws)
    entry.append(target.newItemObject('CapabilitiesLen', length, field=field))
    _, size, field = PduFieldParse(FieldTpUintVar, target, source, kws)
    entry.append(target.newItemObject('HeadersLen', size, field=field))

    if length:
        _, cal, field = PduFieldParse(FieldTpUintVar, target, source, kws)
        entry.append('Length', cal, field=field)
        left = kws['offset'] - (offset + length)
        if left:
            objectp = target.newFieldObject(
                'Identifier', field=_newFieldObject(target, kws, size=left))

            ret = WspHandlePduHeader(
                objectp, target, source, kws,
                length=left, table=_WSP_CAPABILITY)
            if ret:
                entry.append(objectp)
                left = kws['offset'] - (offset + length)
                if left:
                    entry.append(
                        target.newFieldObject(
                            'Parameters', '',
                            field=_newFieldObject(target, kws, size=left)))

    if size:
        objectp = target.newFieldObject(
            'Headers', field=_newFieldObject(target, kws, size=size))
        ret = WspHandlePduHeader(objectp, target, source, kws, length=size)
        if ret:
            entry.append(objectp)

    return ret


# - ref 8.2.4.1 of OMA-WAP-TS-WSP-V1_0-20020920-c
# -------------------------------------------
def WapWspDecodePushPdu(entry, target, source, kws):
    offset = kws['offset']
    ret, length, field = PduFieldParse(FieldTpUintVar, target, source, kws)
    if ret:
        entry.append(target.newItemObject('HeadersLen', length, field=field))

    if length > 0:
        # ContentType
        ret, _ = WapWspContentType(entry, target, source, kws, wsp=True)
        consumed = kws['offset'] - offset
        if consumed != length:
            # Headers
            ret = WspHandlePduHeader(entry, target, source,
                                     kws, length=length - consumed)
            consumed = kws['offset'] - offset
            if ret and consumed != length:
                objectp = entry.getLastObject()

                ret, objp = MmsEncPduParseToObject(
                    source, target, 0, None, kws)
                if ret:
                    objectp.appendChildren(objp)

    if ret:
        objectp.adjust(size=kws['offset'] - offset)

    return ret


# - ref 8.2.1 of OMA-WAP-TS_WSP-V1_0-20020920-C
# -------------------------------------------
def _WapWspDecodePdu(entry, target, source=None, data=None, **kws):
    if source is None and data:
        source = target.newFile(data)

    # FIXME: implement all the table entries?
    _TABLE = {
        0x00: None,  # Reserved
        0x01: WapWspDecodeConnectPdu,  # Connect
        0x02: None,  # ConnectReply
        0x03: None,  # Redirect
        0x04: None,  # Reply
        0x05: None,  # Disconnect
        0x06: WapWspDecodePushPdu,  # Push
        0x07: None,  # ConfirmedPush
        0x08: None,  # Suspend
        0x09: None,  # Resume
        # - 0x10 -> 0x3f: Unassigned
        0x40: None,  # get
        0x41: None,  # Options (Get PDU)
        0x42: None,  # Head (Get PDU)
        0x43: None,  # Delete (Get PDU)
        0x44: None,  # Trace (Get PDU)
        # - 0x45 - 0x4f: Unassigned (Get PDU)
        # - 0x50 - 0x5f: Extended Method (Get PDU)
        0x60: None,  # Post
        0x61: None,  # Put (Post PDU)
        # - 0x62 - 0x6f: Unassigned (Post PDU)
        # - 0x70 - 0x7f: Extended method (Post PDU)
        0x80: None,  # Data Fragment DPU
        # - 0x81 - 0xff: Reserved
    }

    kws['start'] += kws['offset']
    kws['offset'] = 0

    flag = ord(readWithOffset(target, source, 1, kws))
    if _TABLE.get(flag):
        tid = ord(readWithOffset(target, source, 1, kws))

        return _TABLE[flag](entry, target, source, kws)

    return False


# MMS Decoder process

# - refer 7.3.2 of OMA-TS-MMS-ENC-V1_3-20050927-C
# Applic-Id = Text-string
# - refer 7.3.6 of OMA-TS-MMS-ENC-V1_3-20050927-C
# Cancel-id-value = Text-string
# - refer 7.3.29 of OMA-TS-MMS-ENC-V1_3-20050927-C
# Message-ID-value = Text-string
# - refer 7.3.41 of OMA-TS-MMS-ENC-V1_3-20050927-C
# Replace-id-value = Text-string
# - refer 7.3.42 of OMA-TS-MMS-ENC-V1_3-20050927-C
# Applic-Id = Text-string
# - refer 73.45 of OMA-TS-MMS-ENC-V1_3-20050927-C
# Reply-charging-ID-value = Text-string
# - refer to 7.3.59 of OMA-TS-MMS-ENC-V1_3-20050927-C
# Store-status-text-value = Encoded-string-value
# - refer 7.3.63 of OMA-TS-MMS-ENC-V1_3-20050927-C
# Transaction-id-value = Text-string
# - refer Appdendix D of OMA-TS-MMS-ENC-V1_3-20050927-C
# X-Mms-Aux-Applic-Info = Text-string
# -------------------------------------------
def MmsEncString(entry, target, source, kws):
    return FieldEncParse(entry, target, source, kws, FieldTpTextString)


# - refer 7.3.1 of OMA-TS-MMS-ENC-V1_3-20050927-C
# Adaptation-allowed-value = Yes | No
# - refer 7.3.16 of OMA-TS-MMS-ENC-V1_3-20050927-C
# DRM-content-value = Yes | No
# - refer 7.3.17 of OMA-TS-MMS-ENC-V1_3-20050927-C
# Distribution-indicator-value = Yes | No
# - refer 7.3.36 of OMA-TS-MMS-ENC-V1_3-20050927-C
# Mbox-quotas-req = Yes | No
# - refer 7.3.37 of OMA-TS-MMS-ENC-V1_3-20050927-C
# Read-report-value = Yes | No
# - refer 7.3.47 of OMA-TS-MMS-ENC-V1_3-20050927-C
# Report-allowed-value = Yes | No
# - refer 7.3.56 of OMA-TS-MMS-ENC-V1_3-20050927-C
# Store-sent-value = Yes | No
# - refer to 7.3.57 of OMA-TS-MMS-ENC-V1_3-20050927-C
# MM-stored-value = Yes | No
# - refer 7.3.62 of OMA-TS-MMS-ENC-V1_3-20050927-C
# Mbox-totals-req = Yes | No
# -------------------------------------------
def MmsEncYesOrNo(entry, target, source, kws):
    return FieldEncParse(entry, target, source, kws, FieldTpYesOrNo)


def MmsEnvIdentifiedValue(entry, target, source, kws, subject, maps, cb=None):
    offset = kws['offset']
    objectp = _assObject(target, source, kws)

    identifier = ord(readWithOffset(target, source, 1, kws))
    if identifier in maps:
        text = maps.get(identifier)
    elif cb is not None:
        text = cb(entry, target, source, kws, identifier)
    else:
        text = 'unknown'

    objectp.appendChildren(target.newItemObject(subject, text,
                           field=_newFieldObject(target, kws, fixed=1)))
    objectp.update(content='%s (0x%02x)' % (text, identifier),
                   field=_newFieldObject(target, kws, offset))
    entry.appendChildren(objectp)

    return True


# - refer 7.3.5 of OMA-TS-MMS-ENC-V1_3-20050927-C
# Bcc-Value = Encoded-string-value
# - refer 7.3.8 of OMA-TS-MMS-ENC-V1_3-20050927-C
# Cc-value = Encoded-string-value
# - refer 7.3.49 of OMA-TS-MMS-ENC-V1_3-20050927-C
# - With M-Mbox-Delete.conf and M-Delete.conf
# Response-text-value = Encoded-string-value
# - With M-Mbox-Delete.conf and M-Delete.conf
# Response-text-Del-value = Value-length Status-count-value
#                           Response-text-Del-value
# - refer 7.3.19 of OMA-TS-MMS-ENC-V1_3-20050927-C
# Encoded-string-value = Text-string | Value-length Char-set Text-string
# -------------------------------------------
def MmsEncTextValue(entry, target, source, kws, skip_ass=False, ass=-1):
    offset = kws['offset']

    objectp = _assObject(target, source, kws)
    ret, _, _ = PduFieldParse(FieldTpValueLength, target, source, kws)
    if ret:
        # Encoded-string-value = Value-length Char-set Text-string
        ret, charset, field = PduFieldParse(FieldTpWellKnownCharset,
                                            target, source, kws)
        if ret:
            objectp.appendChildren(target.newItemObject('Charset',
                                   charset, field=field))
            ret, text, field = PduFieldParse(FieldTpTextString,
                                             target, source, kws)
            if ret:
                objectp.append(target.newItemObject('Text', text, field))

    if not ret:
        # Encoded-string-value = Text-string
        ret, text, _ = PduFieldParse(FieldTpTextString, target, source, kws)

    if ret:
        objectp.update(
            content=text, field=_newFieldObject(target, kws, offset))
        entry.appendChildren(objectp)

    return ret


def MmsEnvTokenWithLength(entry, target, source, kws, tp, maps, subject=None):
    offset = kws['offset']
    objectp = _assObject(target, source, kws)

    length = ord(readWithOffset(target, source, 1, kws))
    objectp.appendChildren(
        target.newItemObject(
            'length', length,
            field=_newFieldObject(target, kws, size=1, fixed=1)))

    token = ord(readWithOffset(target, source, 1, kws))
    objectp.appendChildren(
        target.newItemObject(
            'token',
            '%s (0x%02x)' % (maps.get(token, 'unknown'), token),
            field=_newFieldObject(target, kws, size=1, fixed=1)))

    ret, value, field = PduFieldParse(tp, target, source, kws)
    if ret:
        if subject is None:
            subject = _Tp2Subject(tp)

        objectp.appendChildren(target.newItemObject(subject,
                               value, field=field))
        objectp.update(content=value,
                       field=_newFieldObject(target, kws, offset))
        entry.appendChildren(objectp)

    return ret


# - refer 7.3.10 of OMA-TS-MMS-ENC-V1_3-20050927-C
# Content-location-value=Uri-value | value-length Status-count-value
#                                    Content-location-value
# -------------------------------------------
def MmsEncContentLocation(entry, target, source, kws):
    offset = kws['offset']

    objectp = _assObject(target, source, kws)
    if kws.get('type', 'unknown') == 'm-mbox-delete-conf':
        ret, _, _ = PduFieldParse(FieldTpValueLength, target, source, kws)
        if ret:
            ret, text, _ = PduFieldParse(
                FieldTpTextString, target, source, kws)
    else:
        ret, text, _ = PduFieldParse(FieldTpTextString, target, source, kws)

    if ret:
        objectp.update(
            content=text, field=_newFieldObject(target, kws, offset))
        entry.appendChildren(objectp)

    return ret


# - refer 7.3.12 of OMA-TS-MMS-ENC-V1_3-20050927-C
# Date-value = Long-integer
# -------------------------------------------
def MmsEncDate(entry, target, source, kws):
    offset = kws['offset']
    objectp = _assObject(target, source, kws)

    ret, text, field = PduFieldParse(FieldTpDateValue, target, source, kws)
    if ret:
        objectp.appendChildren(target.newItemObject('date', text, field=field))
        objectp.update(
            content=text, field=_newFieldObject(target, kws, offset))
        entry.appendChildren(objectp)

    return ret


# - refer 7.3.13 of OMA-TS-MMS-ENC-V1_3-20050927-C
# Delivery-report-value = Yes | No
# -------------------------------------------
def MmsEncDeliveryReport(entry, target, source, kws):
    offset = kws['offset']
    objectp = _assObject(target, source, kws)

    ret, text, field = PduFieldParse(FieldTpYesOrNo, target, source, kws)
    if ret:
        objectp.appendChildren(target.newItemObject('report',
                               text, field=field))
        objectp.update(
            content=text, field=_newFieldObject(target, kws, offset))
        entry.appendChildren(objectp)

    return ret


# - refer 7.3.14 of OMA-TS-MMS-ENC-V1_3-20050927-C
# Delivery-time-value = Value-length (Absolute-token Date-value
#                                     | Relative-token Delta-seconds-value)
# - refer 7.3.20 of OMA-TS-MMS-ENC-V1_3-20050927-C
# Expiry-value = Value-length (Absolute-token Date-value
#                              | Relative-token Delta-seconds-value)
# Absolute-token = <Octet 128>
# Relative-token = <Octet 129>
# - refer 7.3.44 of OMA-TS-MMS-ENC-V1_3-20050927-C
# Reply-charging-deadline-value = Value-length (Absolute-token Date-value
#                                 | Relative-token Delta-seconds-value)
# -------------------------------------------
def MmsEncDeliveryTime(entry, target, source, kws):
    offset = kws['offset']
    objectp = _assObject(target, source, kws)

    length = ord(readWithOffset(target, source, 1, kws))
    objectp.appendChildren(
        target.newItemObject(
            'length', length,
            field=_newFieldObject(target, kws, size=1, fixed=1)))

    token = ord(readWithOffset(target, source, 1, kws))
    objectp.appendChildren(
        target.newItemObject(
            'token', token,
            field=_newFieldObject(target, kws, size=1, fixed=1)))

    if token == 0x80:
        ret, text, field = PduFieldParse(FieldTpDateValue, target, source, kws)
        date = '(absolute) ' + text
    else:
        ret, text, field = PduFieldParse(FieldTpDeltaSecond,
                                         target, source, kws)
        date = '(relative) %d' % text

    if ret:
        objectp.appendChildren(target.newItemObject('date', date, field=field))
        objectp.update(
            content=date, field=_newFieldObject(target, kws, offset))
        entry.appendChildren(objectp)

    return ret


# - refer 7.3.21 of OMA-TS-MMS-ENC-V1_3-20050927-C
# From-value = Value-length (Address-present-token Encoded-string-value
#                            | Insert-address-token)
# Address-present-token = <Oct 128>
# Insert-address-token = <Oct 129>
# -------------------------------------------
def MmsEncFrom(entry, target, source, kws):
    offset = kws['offset']
    objectp = _assObject(target, source, kws)

    length = ord(readWithOffset(target, source, 1, kws))
    if kws['configs'].get(_ENABLE_DETAILS):
        objectp.appendChildren(
            target.newItemObject(
                'length',
                length, field=_newFieldObject(target, kws, offset + 1, 1)))

    token = ord(readWithOffset(target, source, 1, kws))
    if kws['configs'].get(_ENABLE_DETAILS):
        objectp.appendChildren(
            target.newItemObject(
                'token', '0x%02x' % token,
                field=_newFieldObject(target, kws, offset + 2, 1)))

    if token == 0x80:
        ret, text, field = PduFieldParse(
            FieldTpTextString, target, source, kws)
        if ret:
            objectp.appendChildren(target.newItemObject('address',
                                   text, field=field))
    elif token == 0x81:
        ret, text = True, '<insert address>'
    else:
        ret = False

    if ret:
        objectp.update(
            content=text, field=_newFieldObject(target, kws, offset))
        entry.appendChildren(objectp)

    return ret


# - refer 7.3.27 of OMA-TS-MMS-ENC-V1_3-20050927-C
# message-class-value = Class-identifier | Token-text
# Class-identifier = Persional | Advertisement | Informational | Auto
# -------------------------------------------
def MmsEncMessageClass(entry, target, source, kws):
    _CLASSIDENTIFIER = {
        0x80: 'Persional',
        0x81: 'Advertisement',
        0x82: 'Informational',
        0x83: 'Auto'
    }

    def _callback(entry, target, source, kws, value):
        if value & 0x80 == 0:
            ret, text, _ = PduFieldParse(
                FieldTpTextString, target, source, kws)
            if ret:
                return text

        return 'unknown'

    return MmsEnvIdentifiedValue(entry, target, source, kws, 'identifier',
                                 _CLASSIDENTIFIER, _callback)


# - refer 7.3.30 of OMA-TS-MMS-ENC-V1_3-20050927-C
# Message-type-value=m-send-req | m-send-conf | ...
# -------------------------------------------
def MmsEncMessageType(entry, target, source, kws):
    _TYPE_VALUE = {
        0x80: 'm-send-req',            0x81: 'm-send-conf',
        0x82: 'm-notification-ind',    0x83: 'm-notifyresp-ind',
        0x84: 'm-retrieve-conf',       0x85: 'm-acknowledge-ind',
        0x86: 'm-delivery-ind',        0x87: 'm-read-rec-ind',
        0x88: 'm-read-orig-ind',       0x89: 'm-forward-req',
        0x90: 'm-forward-conf',        0x91: 'm-mbox-store-req',
        0x92: 'm-mbox-store-conf',     0x93: 'm-mbox-view-req',
        0x94: 'm-mbox-view-conf',      0x95: 'm-mbox-upload-req',
        0x96: 'm-mbox-upload-conf',    0x97: 'm-mbox-delete-req',
        0x98: 'm-mbox-delete-conf',    0x99: 'm-mbox-descr',
        0x9a: 'm-delete-req',          0x9b: 'm-delete-conf',
        0x9c: 'm-cancel-req',          0x9d: 'm-cancel-conf'
    }

    return MmsEnvIdentifiedValue(entry, target, source,
                                 kws, 'type', _TYPE_VALUE)


# - refer 7.3.35 of OMA-TS-MMS-ENC-V1_3-20050927-C
# MMS-version-value = Short-integer
# -------------------------------------------
def MmsEncVersion(entry, target, source, kws):
    return FieldEncParse(entry, target, source, kws, FieldTpVersionValue)


# - refer 7.3.32 of OMA-TS-MMS-ENC-V1_3-20050927-C
# Message-size-value = Long-integer
# -------------------------------------------
def MmsEncMessageSize(entry, target, source, kws):
    return FieldEncParse(entry, target, source, kws, FieldTpLongInteger)


# - refer 7.3.35 of OMA-TS-MMS-ENC-V1_3-20050927-C
# Priority-value = Low | Normal | high
# -------------------------------------------
def MmsEncPriority(entry, target, source, kws):
    _PRIORITY = {
        0x80: 'Low',  0x81: 'Normal', 0x82: 'High'
    }

    return MmsEnvIdentifiedValue(entry, target, source,
                                 kws, 'priority', _PRIORITY)


# - refer 7.3.48 of OMA-TS-MMS-ENC-V1_3-20050927-C
# Response-status-value = OK | Error-unspecified | ...
# -------------------------------------------
def MmsEncResponseStatus(entry, target, source, kws):
    _RESPONSE_STATUS = {
        0x80: 'Ok',
        0x81: 'Error-unspecified (obsolete)',
        0x82: 'Error-service-denied (obsolete)',
        0x83: 'Error-message-format-corrupt (obsolete)',
        0x84: 'Error-sending-address-unresolved (obsolete)',
        0x85: 'Error-message-not-found (obsolete)',
        0x86: 'Error-network-problem (obsolete)',
        0x87: 'Error-content-not-accepted (obsolete)',
        0x88: 'Error-unsupported-message',
        0xc0: 'Error-transient-failure',
        0xc1: 'Error-transient-sending-address-unresolved',
        0xc2: 'Error-transient-message-not-found',
        0xc3: 'Error-transient-network-problem',
        0xc4: 'Error-transient-partial-success',
        0xe0: 'Error-permanent-failure',
        0xe1: 'Error-permanent-service-denied',
        0xe2: 'Error-permanent-message-format-corrupt',
        0xe3: 'Error-permanent-sending-address-unresolved',
        0xe4: 'Error-permanent-message-not-found',
        0xe5: 'Error-permanent-content-not-accepted',
        0xe6: 'Error-permanent-reply-charging-limitations-not-met',
        0xe7: 'Error-permanent-reply-charging-request-not-accepted',
        0xe8: 'Error-permanent-reply-charging-forwarding-denied',
        0xe9: 'Error-permanent-reply-charging-not-support',
        0xea: 'Error-permanent-address-hidding-not-support',
        0xeb: 'Error-permanent-lack-of-prepaid'
    }

    return MmsEnvIdentifiedValue(entry, target, source,
                                 kws, 'status', _RESPONSE_STATUS)


# - refer 7.3.52 of OMA-TS-MMS-ENC-V1_3-20050927-C
# Sender-visibility-value = Hide | Show
# -------------------------------------------
def MmsEncSendervisibility(entry, target, source, kws):
    _VISIBILITY = {0x80: 'Hide', 0x81: 'Show'}

    return MmsEnvIdentifiedValue(entry, target, source,
                                 kws, 'visiblity', _VISIBILITY)


# - refer 7.3.54 of OMA-TS-MMS-ENC-V1_3-20050927-C
# Status-value = Expired | Retrieved | ...
# -------------------------------------------
def MmsEncStatus(entry, target, source, kws):
    _STATUS = {
        0x80: 'Expired',        0x81: 'Retrieved',
        0x82: 'Rejected',       0x83: 'Deferred',
        0x84: 'Unrecognised',   0x85: 'Indeterminated',
        0x86: 'Forwarded',      0x87: 'Unreachable'
    }

    return MmsEnvIdentifiedValue(entry, target, source, kws, 'status', _STATUS)


# - refer 7.3.60 of OMA-TS-MMS-ENC-V1_3-20050927-C
# Subject-value = Encoded-string-value
# -------------------------------------------
def MmsEncSubject(entry, target, source, kws):
    offset = kws['offset']
    objectp = _assObject(target, source, kws)

    ret, length, field = PduFieldParse(FieldTpValueLength, target, source, kws)
    objectp.appendChildren(target.newItemObject('length', length, field=field))
    if ret:
        # Encoded-string-value = Value-length Char-set Text-string
        ret, charset, field = PduFieldParse(FieldTpWellKnownCharset,
                                            target, source, kws)
        if ret:
            objectp.appendChildren(target.newItemObject('charset',
                                   charset, field=field))
            ret, text, field = PduFieldParse(FieldTpTextString,
                                             target, source, kws)
    else:
        ret, text, field = PduFieldParse(
            FieldTpTextString, target, source, kws)

    if ret:
        objectp.appendChildren(target.newItemObject('text', text, field=field))
        objectp.update(
            content=text, field=_newFieldObject(target, kws, offset))
        entry.appendChildren(objectp)

    return ret


# - refer 7.3.61 of OMA-TS-MMS-ENC-V1_3-20050927-C
# To-value = Encoded-string-value
# -------------------------------------------
def MmsEncTo(entry, target, source, kws):
    return FieldEncParse(entry, target, source, kws, FieldTpTextValue)


# - refer 7.3.50 of OMA-TS-MMS-ENC-V1_3-20050927-C
# Retrieve-status-value = Ok | Error-transient-failure
# -------------------------------------------
def MmsEncRetrieveStatus(entry, target, source, kws):
    _STATUS = {
        0x80: 'Ok',
        0xc0: 'Error-transient-failure',
        0xc1: 'Error-transient-message-not-found',
        0xc2: 'Error-transient-network-problem',
        0xe0: 'Error-permanent-failure',
        0xe1: 'Error-permanent-service-denied',
        0xe2: 'Error-permanent-message-not-found',
        0xe3: 'Error-permanent-content-not-accepted'
    }

    def _callback(entry, target, source, kws, value):
        if 0xc3 <= value <= 0xdf or 0xe4 <= value <= 0xff:
            return 'reserved'
        else:
            return 'known'

    return MmsEnvIdentifiedValue(entry, target, source,
                                 kws, 'status', _STATUS, _callback)


# - refer 7.3.51 of OMA-TS-MMS-ENC-V1_3-20050927-C
# Retrieve-text-value = Encoded-string-value
# -------------------------------------------
def MmsEncRetrieveText(entry, target, source, kws):
    return FieldEncParse(entry, target, source, kws, FieldTpTextValue)


# - refer 7.3.3.8 of OMA-TS-MMS-ENC-V1_3-20050927-C
# Read-status-value = Read | Deleted without being Read
# -------------------------------------------
def MmsEncreadStatus(entry, target, source, kws):
    _STATUS = {0x80: 'Read', 0x81: 'Delete without being read'}

    return MmsEnvIdentifiedValue(entry, target, source, kws, 'status', _STATUS)


# - refer 7.3.43 of OMA-TS-MMS-ENC-V1_3-20050927-C
# Reply-charging-value = Requested | Requested text only | ...
# -------------------------------------------
def MmsEncReplyCharging(entry, target, source, kws):
    _CHARGING = {
        0x80: 'Requested',      0x81: 'Requested text only',
        0x82: 'Accepted',       0x83: 'Accepted text only'
    }

    return MmsEnvIdentifiedValue(entry, target, source,
                                 kws, 'status', _CHARGING)


# - refer 7.3.46 of OMA-TS-MMS-ENC-V1_3-20050927-C
# Reply-charging-size-value = Long-integer
# -------------------------------------------
def MmsEncReplyChargingSize(entry, target, source, kws):
    return FieldEncParse(entry, target, source, kws, FieldTpLongInteger)


# - refer 7.3.23 of OMA-TS-MMS-ENC-V1_3-20050927-C
# Previously-sent-by-value = Value-length Forwarded-count-value
#                            Encoded-string-value
# Forwarded-count-value = Integer-value
# -------------------------------------------
def MmsEncPreviouslySentBy(entry, target, source, kws):
    offset = kws['offset']
    objectp = _assObject(target, source, kws)

    length = ord(readWithOffset(target, source, 1, kws))
    objectp.appendChildren(
        target.newItemObject(
            'length', length,
            field=_newFieldObject(target, kws, offset + 1, fixed=1)))
    ret, text, field = PduFieldParse(FieldTpTextValue, target, source, kws)
    if ret:
        objectp.appendChildren(
            target.newItemObject('value', text, field=field))
        objectp.update(
            content=text, field=_newFieldObject(target, kws, offset))
        entry.appendChildren(objectp)

    return ret


# - refer 7.3.24 of OMA-TS-MMS-ENC-V1_3-20050927-C
# Previously-sent-date-value = Value-length Forwarded-count-value Date-value
# -------------------------------------------
def MmsEncPreviouslySentDate(entry, target, source, kws):
    offset = kws['offset']
    objectp = _assObject(target, source, kws)

    length = ord(readWithOffset(target, source, 1, kws))
    objectp.appendChildren(
        target.newItemObject(
            'length', length,
            field=_newFieldObject(target, kws, offset + 1, fixed=1)))
    ret, date, field = PduFieldParse(FieldTpDateValue, target, source, kws)
    if ret:
        objectp.appendChildren(target.newItemObject('date', date, field=field))
        objectp.update(content=date,
                       field=_newFieldObject(target, kws, offset))
        entry.appendChildren(objectp)

    return ret


# - refer 7.3.33 of OMA-TS-MMS-ENC-V1_3-20050927-C
# MM-state-value = Draft | Sent | New | Retrieved | Forwarded
# -------------------------------------------
def MmsEncMMState(entry, target, source, kws):
    _STATE = {
        0x80: 'Draft',     0x81: 'Sent',
        0x82: 'New',       0x83: 'Retrieved',
        0x84: 'Forwarded'
    }

    return MmsEnvIdentifiedValue(entry, target, source, kws, 'state', _STATE)


# - refer 7.3.32
# MM-flags-value = Value-length (Add-token | Remove-token | Filter-token)
#                  Encoded-string-value
# -------------------------------------------
def MmsEncMMflags(entry, target, source, kws):
    _VALUE = {
        0x80: 'Add',
        0x81: 'Remove',
        0x82: 'Filter'
    }

    return MmsEnvTokenWithLength(entry, target, source,
                                 kws, FieldTpTextValue, _VALUE)


# - refer 7.3.58 of OMA-TS-MMS-ENC-V1_3-20050927-C
# MM-stored-value = Success | Error-transient-failure | ...
# -------------------------------------------
def MmsEncMmsStoreStatus(entry, target, source, kws):
    _STATUS = {
        0x80: 'Success',
        0xc0: 'Error-transient-failure',
        0xc1: 'Error-transient-network-problem',
        0xe0: 'Error-permanent-failure',
        0xe1: 'Error-permanent-service-denied',
        0xe2: 'Error-permanent-message-format-corrupt',
        0xe3: 'Error-permanent-message-not-found',
        0xe4: 'Error-permanent-mmbox-full'
    }

    return MmsEnvIdentifiedValue(entry, target, source, kws, 'status', _STATUS)


# - refer to 7.3.3 of OMA-TS-MMS-ENC-V1_3-20050927-C
# Attributes-value = X-mms-Applic-Id | X-Mms-Aux-Applic-Id | ...
# -------------------------------------------
def MmsEncAttributes(entry, target, source, kws):
    # FIXME: to implement this?
    return MmsEncString(entry, target, source, kws)


# - refer 7.3.26 of OMA-TS-MMS-ENC-V1_3-20050927-C
# Mbox-totals-value = Value-length (Message-total-token | Size-total-token)
#                     Integer-value
# -------------------------------------------
def MmsMboxTotals(entry, target, source, kws):
    _TOTALS = {0x80: 'Message Total', 0x81: 'Size Total'}

    return MmsEnvIdentifiedValue(entry, target, source, kws, 'totals', _TOTALS)


# - refer 7.3.25 of OMA-TS-MMS-ENC-V1_3-20050927-C
# Mbox-quotas-value = Value-length (Message-quota-token
#                     | Size-quota-token) Integer-value
# -------------------------------------------
def MmsEncMboxQuotas(entry, target, source, kws):
    _QUOTA = {0x80: 'Message Quota', 0x81: 'Size Quota'}

    return MmsEnvIdentifiedValue(entry, target, source, kws, 'quota', _QUOTA)


# - refer 7.3.28 of OMA-TS-MMS-ENC-V1_3-20050927-C
# Message-count-value = Integer-value
# -------------------------------------------
def MmsEncMessageCount(entry, target, source, kws):
    return FieldEncParse(entry, target, source, kws, FieldTpInteger)


# - refer 7.3.53 of OMA-TS-MMS-ENC-V1_3-20050927-C
# Index-value = Integer-value
# -------------------------------------------
def MmsEncStart(entry, target, source, kws):
    return FieldEncParse(entry, target, source, kws, FieldTpInteger)


# - refer 7.3.18 of OMA-TS-MMS-ENC-V1_3-20050927-C
# Element-Descriptor-value = Value-length Content-Reference-value *(parameter)
# Content-Reference-value = Text-string
# parameter = parameter-name parameter-value
# parameter-name = Short-integer | Text-string
# parameter-value = constrained-encoding | Text-string
# -------------------------------------------
def MmsEncElementDescriptor(entry, target, source, kws):
    offset = kws['offset']
    objectp = _assObject(target, source, kws)

    ret, length, field = PduFieldParse(FieldTpValueLength, target, source, kws)
    if ret:
        objectp.appendChildren(target.newItemObject('length',
                               length, field=field))
        ret, subject, field = PduFieldParse(FieldTpTextString,
                                            target, source, kws)

    if ret:
        # FIXME: add the analysis of *(parameter)
        objectp.appendChildren(target.newItemObject('content',
                               subject, field=field))
        entry.appendChildren(objectp)

    return ret


# refer 7.3.22 of OMA-TS-MMS-ENC-V1_3-20050927-C
# Index-value = Integer-value
# -------------------------------------------
def MmsEncLimit(entry, target, source, kws):
    return FieldEncParse(entry, target, source, kws, FieldTpInteger)


# - refer 7.3.39 of OMA-TS-MMS-ENC-V1_3-20050927-C
# Recommended-Retrieval-Mode-Value = Manual
# Manual = 0x80
# -------------------------------------------
def MmsEncRecommendedRetrievalMode(entry, target, source, kws):
    _MANUAL = {0x80: 'Manual'}

    return MmsEnvIdentifiedValue(entry, target, source, kws, 'mode', _MANUAL)


# - refer 7.3.55 of OMA-TS-MMS-ENC-V1_3-20050927-C
# Status-text-value = Encoded-string-value
# -------------------------------------------
def MmsEncStatusText(entry, target, source, kws):
    return FieldEncParse(entry, target, source, kws, FieldTpTextValue)


# - refer 7.3.9 of OMA-TS-MMS-ENC-V1_3-20050927-C
# Content-class-value = text | image-basic | image-rich | ...
# -------------------------------------------
def MmsEncContentClass(entry, target, source, kws):
    _VALUE = {
        0x80: 'text',           0x81: 'image-basic',
        0x82: 'image-rich',     0x83: 'video-basic',
        0x84: 'video-rich',     0x85: 'megapixel',
        0x86: 'content-basic',  0x87: 'content-rich'
    }

    return MmsEnvTokenWithLength(entry, target, source,
                                 kws, FieldTpInteger, _VALUE)


# - refer 7.3.7 of OMA-TS-MMS-ENC-V1_3-20050927-C
# Cancel-status-value = Cancel Request Successfully Received
#                       | Cancel Request Corrupted
# -------------------------------------------
def MmsEncCancelStatus(entry, target, source, kws):
    _VALUE = {
        0x80: 'Canncel Request Successfully Received',
        0x81: 'Canncel Request Corrupted'
    }

    return MmsEnvTokenWithLength(entry, target, source,
                                 kws, FieldTpInteger, _VALUE)


# -------------------------------------------
# refer to 7.4 of OMA-TS-MMS-ENC-V1_3-20050927-C
_FIELD_ASSIGNMENT = {
    0x01: ('Bcc', MmsEncTextValue),
    0x02: ('Cc', MmsEncTextValue),
    0x03: ('X-Mms-Content-Location', MmsEncContentLocation),
    0x04: ('Content-Type', WapWspContentType),
    0x05: ('Date', MmsEncDate),
    0x06: ('X-Mms-Delivery-Report', MmsEncDeliveryReport),
    0x07: ('X-Mms-Delivery-Time', MmsEncDeliveryTime),
    0x08: ('X-Mms-Expiry', MmsEncDeliveryTime),
    0x09: ('From', MmsEncFrom),
    0x0a: ('X-Mms-Message-Class', MmsEncMessageClass),
    0x0b: ('Message-ID', MmsEncString),
    0x0c: ('X-Mms-Message-Type', MmsEncMessageType),
    0x0d: ('X-Mms-MMS-Version', MmsEncVersion),
    0x0e: ('X-Mms-Message-Size', MmsEncMessageSize),
    0x0f: ('X-Mms-Priority', MmsEncPriority),
    0x10: ('X-Mms-Read-Report', MmsEncYesOrNo),
    0x11: ('X-Mms-Report-Allowed', MmsEncYesOrNo),
    0x12: ('X-Mms-Response-Status', MmsEncResponseStatus),
    0x13: ('X-Mms_Response-Text', MmsEncTextValue),
    0x14: ('X-Mms-Sender-Visibility', MmsEncSendervisibility),
    0x15: ('X-Mms-Status', MmsEncStatus),
    0x16: ('Subject', MmsEncSubject),
    0x17: ('To', MmsEncTo),
    0x18: ('X-Mms-Transaction-Id', MmsEncString),
    0x19: ('X-Mms-Retrieve-Status', MmsEncRetrieveStatus),
    0x1a: ('X-Mms-Retrieve-Text', MmsEncRetrieveText),
    0x1b: ('X-Mms-Read-Status', MmsEncreadStatus),
    0x1c: ('X-Mms-Reply-charging', MmsEncReplyCharging),
    0x1d: ('X-Mms-Reply-charging-Deadline', MmsEncDeliveryTime),
    0x1e: ('X-Mms-Reply-charging-ID', MmsEncString),
    0x1f: ('X-Mms-Reply-charging-Size', MmsEncReplyChargingSize),
    0x20: ('X-Mms-Previously-Sent-by', MmsEncPreviouslySentBy),
    0x21: ('X-Mms-Previously-Sent-Date', MmsEncPreviouslySentDate),
    0x22: ('X-Mms-Store', MmsEncYesOrNo),
    0x23: ('X-Mms-MM-State', MmsEncMMState),
    0x24: ('X-Mms-MM-Flags', MmsEncMMflags),
    0x25: ('X-Mms-Store-Status', MmsEncMmsStoreStatus),
    0x26: ('X-Mms-Store-Status-Text', MmsEncString),
    0x27: ('X-Mms-Stored', MmsEncYesOrNo),
    0x28: ('X-Mms-Attributes', MmsEncAttributes),
    0x29: ('X-Mms-Totals', MmsEncYesOrNo),
    0x2a: ('X-Mms-Mbox-Totals', MmsEncYesOrNo),
    0x2b: ('X-Mms-Quotas', MmsEncYesOrNo),
    0x2c: ('X-Mms-Mbox-Quotas', MmsEncMboxQuotas),
    0x2d: ('X-Mms-Message-Count', MmsEncMessageCount),
    0x2e: ('Content', None),  # FIXME: how aobut this?
    0x2f: ('X-Mms-Start', MmsEncStart),
    0x30: ('Additional-headers', None),  # FIXME: like 0x2e?
    0x31: ('X-Mms-Distribution-Indicator', MmsEncYesOrNo),
    0x32: ('X-Mms-Element-Descriptor', MmsEncElementDescriptor),
    0x33: ('X-Mms-Limit', MmsEncLimit),
    0x34: ('X-Mms-Recommended-Retrieval-Mode', MmsEncRecommendedRetrievalMode),
    0x35: ('X-Mms-Recommended-Retrieval-Mode-Text',
           MmsEncRecommendedRetrievalMode),
    0x36: ('X-Mms-Status-Text', MmsEncStatusText),
    0x37: ('X-Mms-Applic-ID', MmsEncString),
    0x38: ('X-Mms-Reply-Applic-ID', MmsEncString),
    0x39: ('X-Mms-Aux-Applic-Info', MmsEncString),
    0x3a: ('X-Mms-Content-Class', MmsEncContentClass),
    0x3b: ('X-Mms-DRM-Content', MmsEncYesOrNo),
    0x3c: ('X-Mms-Adaptation-Allowed', MmsEncYesOrNo),
    0x3d: ('X-Mms-Replace-ID', MmsEncString),
    0x3e: ('X-Mms-Cancel-ID', MmsEncString),
    0x3f: ('X-Mms-Cancel-Status', MmsEncCancelStatus)
}


def MmsEncPduParseToObject(source, target, k, notify, kws, tags=False):
    offset = kws['offset']
    ret, objectp = True, target.newItemObject('MMS #%d' % k)

    t8c = False
    if tags:
        if 'tags' in kws and isinstance(kws['tags'], dict):
            ktags = kws['tags']
        else:
            ktags = dict()

    while ret and kws['offset'] < len(source):
        ass = ord(target.readFile(source, 1, **kws))
        if kws['configs'].get(_ENABLE_DEBUG):
            print '>> TAG: [%x] %d, start=%d' % (
                ass, ass, kws['start'] + kws['offset'])

        koff = kws['offset']
        # only support one message-type once
        if ass == 0x8c and t8c:
            break
        elif (ass & 0x80) == 0:
            content, field2 = None, None
            ret, subject, field = PduFieldParse(FieldTpTextString,
                                                target, source, kws)
            if ret:
                ret, content, field2 = PduFieldParse(FieldTpTextString,
                                                     target, source, kws)
                if ret:
                    obj = target.newItemObject(subject, content,
                                               field=field + field2)
                    objectp.appendChildren(obj)
                    if content:
                        obj.appendChildren(target.newItemObject(subject,
                                           field=field))
                        obj.appendChildren(target.newItemObject(content,
                                           field=field2))
        elif ass >= 0x80 and (ass & 0x7f) in _FIELD_ASSIGNMENT:
            key, func = _FIELD_ASSIGNMENT[ass & 0x7f]
            if kws['configs'].get(_ENABLE_DEBUG) == 2:
                print "ToObj ==>", key, func

            ret = func(objectp, target, source, kws)
            if isinstance(ret, (list, tuple)):
                ret = ret[0]
            # record message-type: each MMS has one 0x8c assignment
            if ass == 0x8c and ret:
                t8c = True
        else:
            # unknown assignments, decode with 'Assignemnt=String'
            if kws['configs'].get(_ENABLE_DEBUG) == 2:
                print "Unknown Assignment=%lx" % ass

            kws['offset'] += 1
            ret, content, field = PduFieldParse(FieldTpTextString,
                                                target, source, kws)
            if ret:
                subject = 'Unknown (0x%02x)' % ass
                fieldass = _newFieldObject(target, kws, 1)

                obj = target.newItemObject(subject, content,
                                           field=fieldass + field)
                obj.appendChildren(target.newItemObject(subject,
                                   field=fieldass))
                obj.appendChildren(target.newItemObject(content, field=field))
                objectp.appendChildren(obj)

        if kws.get('multipart', False):
            del kws['multipart']
            ret = WspDecodeMultipartData(objectp, target, source, notify, kws)
            if ret and kws['offset'] < len(source):
                ass = ord(target.readFile(source, 1, **kws))
                if ass != 0x8c:
                    objectp.appendChildren(
                        target.newItemObject(
                            'Media Type',
                            field=_newFieldObject(
                                target, kws,
                                size=len(source) - kws['offset'])))

                    kws['offset'] = len(source)

        if tags:
            koff += 1
            ktags[ass] = target.readFile(
                source, kws['offset'] - koff, off=koff)

    if ret:
        objectp.update(field=_newFieldObject(target, kws, offset))

    if tags:
        kws['tags'] = ktags
        return ret, ktags

    return ret, objectp


class _MmsDecorder:
    # export generator interfaces
    __creator__ = ('Indication Generator',)
    __referral__ = (('DecodePdu', _WapWspDecodePdu),)

    # commonly each field assignment is valid, limit it to simplify the parsing
    ALLOWED_ASSIGNMENTS = (0x8c, 0x84)

    def Execute(self, source, target, notify, **kws):
        if '_i' in kws:
            configs = kws['_i'].configs()
            kws['configs'] = configs

        plxtype = kws.get('plxtype')
        if plxtype == 'formatter':
            return self._doFormat(source, target, notify, kws)
        elif plxtype == 'generator':
            return self._doGenerate(source, target, notify, kws)
        else:
            return False

    def _doFormat(self, source, target, notify, kws):
        entry = target.getEntry()

        k, ret, objectp = 1, True, None
        try:
            ass = ord(target.readFile(source, 1, **kws))
            if ass not in self.ALLOWED_ASSIGNMENTS:
                ret = False
        except:
            return False

        kws['offset'] = 0
        if 'multipart' in kws:
            del kws['multipart']

        while ret and kws['offset'] < len(source):
            kws['type'] = 'unknown'
            try:
                ret, objectp = MmsEncPduParseToObject(source, target,
                                                      k, notify, kws)
            except:
                ret = False

            if ret:
                if kws['offset'] < len(source) or k > 1:
                    entry.append(objectp)
                else:
                    entry.append(objectp.getChildren())

            k += 1

        return ret

    def _doGenerate(self, source, target, notify, kws):
        # TODO: Add the interface name check if multiple supports

        objfmt = kws.get('_i').objfmt
        # _doFormat works, remove all try-except handlers
        kws['start'] = 0
        kws['offset'] = 0
        kws['type'] = 'unknown'

        if 'multipart' in kws:
            del kws['multipart']

        ret, tags = MmsEncPduParseToObject(source, objfmt, 0,
                                           notify, kws, True)
        if not ret:
            return False

        if 0x98 in tags:
            _resource = '''
<?xml version="1.0"?>
<resource>
    <object class="wxPanel" name="panel_1">
        <style>wxTAB_TRAVERSAL</style>
        <object class="wxFlexGridSizer" name="grid_sizer_1">
            <rows>2</rows>
            <cols>2</cols>
            <vgap>5</vgap>
            <hgap>5</hgap>
            <object class="sizeritem">
                <object class="wxStaticText" name="label_1">
                    <attribute>1</attribute>
                    <label>X-Mms-Transaction-ID</label>
                </object>
            </object>
            <object class="sizeritem">
                <option>1</option>
                <flag>wxEXPAND|wxALL</flag>
                <object class="wxTextCtrl" name="tid">
                    <value>%s</value>
                </object>
            </object>
            <object class="sizeritem">
                <object class="wxStaticText" name="label_2">
                    <attribute>1</attribute>
                    <label>X-Mms-Content-Location</label>
                </object>
            </object>
            <object class="sizeritem">
                <option>1</option>
                <flag>wxEXPAND|wxALL</flag>
                <object class="wxTextCtrl" name="location">
                    <size>200,-1</size>
                    <value>file://m-retreve-conf.txt</value>
                </object>
            </object>
        </object>
    </object>
</resource>
''' % str(tags[0x98][:-1])

            _dialog = kws['_i'].dialog
            if _dialog:
                opts = _dialog(_resource)
                if opts is None:
                    return False
                else:
                    tags[0x98] = opts.get('tid', tags[0x98])
                    tags[0x98] += chr(0x00)
                    tags[0x83] = opts.get('location',
                                          'file://m-retreve-conf.txt')
                    tags[0x83] += chr(0x00)

        GENERATED_ASS = (0x98, 0x8d, 0x89, 0x96, 0x8a, 0x8e, 0x88, 0x83)

        def put(s):
            target.write(s)

        # write pdu type
        put(chr(0x8c))
        put(chr(0x82))

        for ass in GENERATED_ASS:
            if ass in tags:
                put(chr(ass))
                put(tags[ass])

        return True


def fmtService():
    return _MmsDecorder
